The Joy of Using Docker

I’ve written about Docker before, as I am a big fan of it. And for this post, I’m going to talk about some practical situations in which I’ve used Docker in real life, both for testing and software development!

Docker Logo

But first, let’s recap what Docker IS and IS NOT:

  • Docker containers spin up quickly (1-2 seconds or less)
  • Docker containers DO have separated process tress and filesystems
  • Docker containers ARE NOT virtual machines
  • Docker containers ARE intended to be ephemeral. (short-lived)
  • You CAN, however, mount filesystems from the host machine into Docker, so those files can live on after the container shuts down (or is killed).
  • You SHOULD only run one service per Docker container.

Everybody got that? Good. Now, let’s get into some real life things I’ve used Docker for.

Experimenting in Linux

Want to test out some commands or maybe a shell script that you’re worried might be destructive? No worries, try it in a Docker container, and if you nuke the filesystem, there will be no long-term consequences.

#
# Start a container with Alpine Linux
#
$ docker run -it alpine

#
# Let's do something dumb
#
$ rm /bin/ls 
$ ls -l
/bin/sh: ls: not found

#
# Just exit the container, restart it, and our filesystem is back!
#
/ # exit 
[unifi:~/tmp ] $ docker run -it alpine
/ # ls
bin    dev    etc    home   lib    media  mnt    proc   root   run    sbin   srv    sys    tmp    usr    var

And all of the above takes just a couple of seconds! This works with other Linux distros as well, such as CentOS and Unbuntu–just change your Docker command accordingly:

docker run -it centos
docker run -it ubuntu

Yes, that means you could run CentOS in a container under Ubuntu or vice-versa. Docker doesn’t care. 🙂

Serving Static Content

Want to serve up static content for building a website? No problem–there is an nginx container that is part of Docker Hub which makes this painless:

docker run -v htdocs/:/usr/share/nginx/html -p 8080:80 -d nginx

Now, just go to http://localhost:8080/ in your web broswer and you’ll see your site’s content.

Would you like to test out a custom configuration with nginx as well? Just use -v again:

docker run \
   -v htdocs/:/usr/share/nginx/html \
   -v nginx.conf:/etc/nginx/nginx.conf \
   -p 8080:80 -d nginx

Each time you do this, the container will spin up in 1-2 seconds at the most. This makes setup and teardown much easier, as you will not need to keep standing up VMs in Vagrant.

Complex Development Environments

SEPTA Logo

If you are building a full blown web application, you’ll have multiple moving parts. Using my website SEPTA Stats as an example, it has Nginx for static assets, PHP for server side code execution, Redis for caching, and Splunk for data ingestion and report generation. By putting everything into a single Docker Compose file, I can now spin up that entire environment with a single docker-compose up -d command. That’s a huge step up from having to use Ansible to set things up in a virtual machine in the past.

Software Updates

When I ported SEPTA Stats over to Docker, I noticed that I was running PHP 5.6. The line in my docker-compose.yml file looked like this:

image: php:5.6.37-fpm

I wanted to switch to PHP 7.2 without causing excessive downtime. In the old days, that would involve standing up a new VM somewhere with PHP 7.2, deploying my code, fixing all of the bugs, deploying to my production server, and then shutting down the webserver. But with Docker, it’s so much easier. I started by replacing the above line for my PHP container with this one:

image: php:7.2.8-fpm

I then killed, removed, and restarted the PHP container on my Mac. I then started loading pages on http://localhost:8080/ and fixing problems in the PHP code as I found them. Once I fixed all of the problems, I committed the PHP code and my changed docker-compose.yml file and deployed them on the production server as follows:

docker-compose kill php
docker-compose rm -f php
git pull
docker-compose up -d php

The above only took a couple of seconds. Fantastic!

Complex Development Environments, Part 2

Jake "The Snake" Roberts
The Creator of Python

You should be using Python 3. But lets say you’re working on a system that has Python 2 installed–it can sometimes be challenging to have two version of Python that co-exist. With Docker, you can work around that!

Let’s say you’re in the directory of your favorite Python 3 project. Go ahead and put this into a Dockerfile:

FROM alpine

RUN apk add python3
COPY requirements.txt /
RUN pip3 install -r /requirements.txt

WORKDIR /mnt

You can now build that Docker container:

docker build . -t test-python3

Once built, the Docker container will contain a copy of Python 3 along with whatever modules your project requires. You can now spin up that container interactively and run Python code inside of it:

$ docker run -it -v $(pwd):/mnt test-python3
/mnt # ./hello.py 
Hello World!

Since your directory has been mounted inside of the Docker container, you can now use your favorite editor on the host machine to your Python code, and run it inside of the Docker container.

If you want the Python script to execute automatically when the container is run, that gets into ENTRYPOINT, which is beyond the scope of this particular post. 🙂

Old Environments

This last use case is not something I’m proud of, but is a reality in many workplaces: legacy code and environments. I once inherited a Node.js project which was written using Node.js 0.10. I had concerns about intentionally installing an old version of Node onto our production servers, as an upgrade might cause a new version of Node to be installed and break things. Docker to the rescue!

Let’s start with building a Dockerfile to install Node 0.10.0 on CentOS:

FROM centos

RUN yum install -y centos-release-scl
RUN yum install -y nodejs010

You can now build and run that container interactively to see that Node.js 0.10 is installed:

$ docker build -t node-0.10 .
$ docker run -it node-0.10
[root@780f33eb99b1 /]# scl enable nodejs010 'bash -c "node -v"'
v0.10.40

Again, this isn’t something that is always a GREAT idea, but at the very least, if you’re forced to run legacy code, you can have that code hosted on a modern server or cloud instance which is not legacy.