Speak to anybody working in DevOps today, and Docker will be one of the hot topics of interest. At its core, Docker is a modular, layered approach to vitalization that runs on top of a host system’s kernel while keeping guest processes and filesystems separated.
This post was born out of a meetup I attended this week. We had a chance to do a live workshop on Jenkins using a Docker image, but—not having using used Docker since I played with it a bit over the summer—I spent most of the night dealing with breaking changes caused by El Capitan and relearning Docker itself. My loss turned out to be a gain in relearning such an important tool.
Basic Setup: Docker and Docker Machine for Mac OS X
What is Docker, exactly? In the words of the Docker Home Page:
Docker is not a vitalization technology; it is a set of tools that makes use of native Linux kernel features such as cgroups and pgroups to separate processes and filesystems from each other. This allows the “illusion” of vitalization, along with modularity, at the cost of requiring a Linux host system.
For developers not using Linux as their dev environment—and for those who do use Linux but want to separate their docker install from their main dev environment—a virtual Linux machine is the starting point to using Docker. Just because we’re using a VM to host the Docker machine doesn’t mean that Docker, when deployed in production, uses VMs—it is simply a convenience for our development environment.
These instructions and all that follow assume a relatively modern version of Mac OS X. Many of them may be transferable to other operating systems, but I give no guarantees of that.
Install or Update VirtualBox
We can install VirtualBox using Homebrew. If you are on El Capitan, keep in mind that previous versions of VirtualBox are no longer compatible with El Capitan, so you’ll have to upgrade to a 5.x version or higher.
$ brew cask install virtualbox $ brew cask update virtualbox
Install Docker Machine
Previously Mac OS X users have used boot2docker to install, run, and manage their Docker machines. However boot2docker has been deprecated, and it is strongly encourages users to switch to Docker Machine.
If you’re new to Docker, this shouldn’t be a problem since there’s no need to migrate. Users who do need to migrate their docker containers from boot2docker to Docker Machine should consult the documentation.
$ brew install docker-machine docker
Installing a Docker Host
Finally, we have to install the actual Docker machine, which under the hood is
Linux VM hosting the Docker daemon, managed entirely by the
command line tool.
Docker Machine makes this easy with the
create subcommand. This subcommand
allows for multiple different drivers, most of which are geared towards users
of cloud providers such as Amazon Web Services and Digital
Ocean. For local development environments, the
driver is all we want. The machine can be given any name, but
dev seems like a
nice choice to start with.
$ docker-machine create -d virtualbox dev $ docker-machine ls
Managing Docker Machines
Lets run through a few basic management tools of Docker Machine before starting with Docker itself. If you want, you can skip to Starting with Docker.
We can list all of the currently installed Docker machines.
$ docker-machine ls NAME ACTIVE DRIVER STATE URL SWARM dev * virtualbox Running tcp://192.168.99.100:2376
Note that besides a list of names, we get important information such as the current status and IP address of each of our machines. If you want just the status or IP of a particular machine, the commands are:
$ docker-machine status dev $ docker-machine ip dev
Starting and Stopping
To start and stop machines are similarly simple:
$ docker-machine start dev $ docker-machine stop dev
This may be important to remember for laptop users, as running a VM, especially with active containers, may drain the battery. If you’re using docker on a cloud-based dev environment, you may be charged for machines left running when you’re done with them. Either way, remember to shut down your dev machines when you aren’t using them!
Starting with Docker
Before we can use the docker command-line tool, we need to tell it what Docker
host to use. The
docker-machine env [machine-name] command lists the
environment variables needed to make this happen; in order to set them for our
current shell session, we run
$ docker-machine env dev export DOCKER_TLS_VERIFY="1" export DOCKER_HOST="tcp://192.168.99.100:2376" export DOCKER_CERT_PATH="/Users/cbeynon/.docker/machine/machines/dev" export DOCKER_MACHINE_NAME="dev" $ eval $(docker-machine env dev)
once a machine is running.
Laptop users who change wifi networks may have to rerun this; otherwise their VM may not be able to access the Internet. We may have to restart the machine, as well.
$ docker-machine restart dev $ eval $(docker-machine env dev)
Our First Docker Container
Now we’re finally ready to start our first Docker container!. We’ll use a
BusyBox container, since its a relatively small download. We use the
run command, pass it the
-it flags for Interactive, virtual Tty, and
the name of the image. Since we (mostly likely) don’t have the image in our
repository of images, Docker will automatically download it from the central
$ docker run -it busybox Unable to find image 'busybox:latest' locally latest: Pulling from library/busybox 039b63dd2cba: Pull complete c51f86c28340: Pull complete Digest: sha256:eb3c0d4680f9213ee5f348ea6d39489a1f85a318a2ae09e012c426f78252a6d2 Status: Downloaded newer image for busybox:latest # uname -a Linux 6abeeae527ab 4.1.12-boot2docker #1 SMP Tue Nov 3 06:03:36 UTC 2015 x86_64 GNU/Linux # ps --help BusyBox v1.24.1 (2015-10-31 17:14:08 UTC) multi-call binary. # exit
Since we ran with the
-it flags we got an interactive shell. We instead could
have passed docker a command to run on the container as follows:
$ docker run busybox echo Hello 世界! Hello 世界!
(That’s a nod to Go coders and Japanese readers :D)
Something More Complicated
We can start up an NGINX container, and then browser to it on a local browser. To do this, we need to bind the desired port on the container (e.g. port 80 for HTTP) to a port on the Docker VM (which can be anything, even port 80 again). Here I’m binding to port 8080, just to show we can bind to a different port—which may be useful if the same Docker VM is running multiple NGINX containers.
$ docker run -d -p 8080:80 nginx $ docker-machine ip dev $ links $(docker-machine ip dev):8080
Obviously, you can use any web browser—even curl—you want. Just be sure to
grab the IP using
docker-machine ip if you’re using a GUI browser.
Let’s finish by closing up our containers and machines.
$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 32f9ad0a68fa nginx "nginx -g 'daemon off" 3 minutes ago Up 3 minutes 0.0.0.0:80->80/tcp, 443/tcp romantic_euclid $ docker stop romantic_euclid $ docker-machine ls NAME ACTIVE DRIVER STATE URL SWARM dev * virtualbox Running tcp://192.168.99.100:2376 $ docker-machine stop dev
I’m going to be writing more about Docker in the coming weeks, including more details on how Docker’s modularity and layering works, and how to build a simple NGINX-based container to serve a Jekyll blog, complete with customizations to the NGINX configuration and SSL keys. Keep a look out for it!