Docker: Containers for the Masses -- using Docker

10 Jun 2014

And now we return to our previously-scheduled blog series "Docker: Containers for the Masses". To bring the reader up to speed with what episodes have already aired:

In today's episode, the real excitement begins with actually using Docker. The reader will be shown how to perform tasks with Docker, how to build images by hand, and using a Dockerfile to build Docker images. Also starring in this episode are images that a user can build that have services running as well as ENTRYPOINT and CMD instructions.

The Docker CLI

The Docker CLI, the tool that one uses to interact with Docker, is very intuitive and has a well-documented help facility that can simply be invoked by running:

$ docker help subcommand

For instance, if one were to need to find out the options on building images:

$ docker help build

Usage: docker build [OPTIONS] PATH | URL | -

Build a new container image from the source code at PATH

  --no-cache=false   Do not use cache when building the image
  -q, --quiet=false  Suppress the verbose output generated by the containers
  --rm=true          Remove intermediate containers after a successful build
  -t, --tag=""       Repository name (and optionally a tag) to be applied to the resulting image in case of success

The following shows basic usage of Docker and some common tasks that a user would perform.

Pulling the base Ubuntu image

The first thing to do is to build images based off of Ubuntu.

(Note: it is simply a preference of the author of this blog to use Ubuntu. There are other base images available for other Linux distributions.)

To obtain a base image, and in this case, the Ubuntu base image, conduct a search to find out the image tag in order to pull the Ubuntu image from the Docker image repository:

$ docker search ubuntu
NAME                         DESCRIPTION                                     STARS   OFFICIAL   TRUSTED
ubuntu                       Official Ubuntu base image                      171
stackbrew/ubuntu             Barebone ubuntu images                          36
crashsystems/gitlab-docker   A trusted, regularly updated build of GitL...   19                 [OK]
dockerfile/ubuntu            Trusted Ubuntu ( Bu...   13                 [OK]

In this example, the list is extensive. The first one from above is selected:

$ docker pull ubuntu
Pulling repository ubuntu
3db9c44f4520: Download complete
6006e6343fad: Download complete
d2099a5ba6c5: Pulling dependent layers
5cf8fd909c6c: Pulling dependent layers
7656cbf56a8c: Pulling dependent layers
cc0067db4f11: Pulling dependent layers
511136ea3c5a: Download complete
086a54ed1641: Download complete
5e9838970f44: Download complete
c47d897f1a44: Download complete
845a7ff0bf5a: Downloading 2.027 MB/40.16 MB 3m59s
e41f31e92d60: Downloading 1.986 MB/39.53 MB 4m6s
101e9f33c3dc: Downloading  7.08 MB/39.17 MB 1m51s
6cfa4d1f33fb: Download complete
35f6dd4dd141: Downloading 1.582 MB/67.48 MB 4m42s
7baf0ef6f14a: Download complete
e497c7c1bfbb: Download complete
... <snip> ...

Launching a container

Once the base Ubuntu image is pulled, it is possible to launch a container using it. In this example, the flags -i -t are specified to indicate running interactively using a pseudo terminal.

$ docker -it run ubuntu /bin/bash

(Note: later in this post, it will be shown how to run a container that runs sshd that will allow a user to log into.)

At this point, changes can be made to the container and any commits will create images representative of the changes made.

One the session is exited, the container also exits.

Analyzing your container

It is now possible to verify the container as well as find out the container ID of the container that is running. In another terminal, run the following:

$ docker ps
849e71a53c2c  ubuntu:14.04  /bin/bash  2 minutes ago  Up 2 minutes         loving_bardeen

Running docker ps with a -a flag can also be used to see all containers, both running and terminated:

$ docker ps -a
CONTAINER ID  IMAGE         COMMAND    CREATED        STATUS                     PORTS  NAMES
7f5881636580  ubuntu:14.04  /bin/bash  3 seconds ago  Exited (0) 1 seconds ago          clever_shockley
94d4341afefa  ubuntu:14.04  /bin/bash  7 seconds ago  Exited (0) 4 seconds ago          happy_bardeen
849e71a53c2c  ubuntu:14.04  /bin/bash  17 hours ago   Up 17 hours                       loving_bardeen

As shown above in the output, the container currently running is still shown as running-- the author of this blog has left it up for 17 hours so fire while writing this part of the post!

Connecting to a container

When a virtual machine is run, one commonly expects to be able to ssh into it. If Docker is run as in the above example, interactively, you see that it is running in a shell that allows you to do work inside of your container. The container can also be can also be connected to it using the following command:

$ docker attach 849e71a53c2c

Inspecting a container

One command that will be often used is the inspection command. This command, when run, will produce a large JSON data structure with every bit of information you would ever want to know about the container. Most often, the primary interest of the author is to use this command to find out either the IP address or what ports docker uses for exposed ports:

$ docker inspect 849e71a53c2c
  "ID": "849e71a53c2ce2d696cb71782bb25528e503c87a150ae87efa71c6265c027879",
  "Created": "2014-06-01T11:44:12.117743232Z",
  "Path": "/bin/bash",
  "Args": [],
  "Config": {
      "Hostname": "849e71a53c2c",
      "Domainname": "",
      "User": "",
      "Memory": 0,
      "MemorySwap": 0,
      "CpuShares": 0,
      "AttachStdin": true,
      "AttachStdout": true,
      "AttachStderr": true,
      "PortSpecs": null,
      "ExposedPorts": null,
      "Tty": true,
      "OpenStdin": true,
      "StdinOnce": true,
      "Env": [
      "Cmd": [
      "Image": "ubuntu",
      "Volumes": null,
      "WorkingDir": "",
      "Entrypoint": null,
      "NetworkDisabled": false,
      "OnBuild": null
  "State": {
      "Running": false,
      "Pid": 0,
      "ExitCode": 0,
      "StartedAt": "2014-06-01T11:44:12.393165329Z",
      "FinishedAt": "2014-06-02T08:27:24.095030218Z"
  "Image": "5cf8fd909c6ccc61199df6dbeb165767b83c23842ef49ca3ef3b81ece1bdce4f",
  "NetworkSettings": {
      "IPAddress": "",
      "IPPrefixLen": 0,
      "Gateway": "",
      "Bridge": "",
      "PortMapping": null,
      "Ports": null
  "ResolvConfPath": "/etc/resolv.conf",
  "HostnamePath": "/var/lib/docker/containers/849e71a53c2ce2d696cb71782bb25528e503c87a150ae87efa71c6265c027879/hostname",
  "HostsPath": "/var/lib/docker/containers/849e71a53c2ce2d696cb71782bb25528e503c87a150ae87efa71c6265c027879/hosts",
  "Name": "/loving_bardeen",
  "Driver": "aufs",
  "ExecDriver": "native-0.2",
  "MountLabel": "",
  "ProcessLabel": "",
  "Volumes": {},
  "VolumesRW": {},
  "HostConfig": {
      "Binds": null,
      "ContainerIDFile": "",
      "LxcConf": [],
      "Privileged": false,
      "PortBindings": {},
      "Links": null,
      "PublishAllPorts": false,
      "Dns": null,
      "DnsSearch": null,
      "VolumesFrom": null,
      "NetworkMode": "bridge"

Committing a container and creating images

As mentioned in the previous example, it would have been possible to commit the container to an image. In order to do this, one will need the container ID. To find this, one of the most used commands you will familiarize yourself with is the docker ps command:

$ docker ps
849e71a53c2c  ubuntu:14.04  /bin/bash  2 minutes ago  Up 2 minutes         loving_bardeen

Once the container ID is known, the container can be committed to an image:

$ docker commit 849e71a53c2c capttofu/ubuntu_example

To verify that there is now an image created from the previous step

$ docker images
REPOSITORY                TAG      IMAGE ID      CREATED        VIRTUAL SIZE
capttofu/ubuntu_example   latest   f54ee24a549a  9 minutes ago  274.3 MB
ubuntu                    12.10    6006e6343fad  2 days ago     172.2 MB
ubuntu                    quantal  6006e6343fad  2 days ago     172.2 MB
ubuntu                    13.10    d2099a5ba6c5  2 days ago     180.2 MB
ubuntu                    saucy    d2099a5ba6c5  2 days ago     180.2 MB
ubuntu                    14.04    5cf8fd909c6c  2 days ago     274.3 MB
ubuntu                    latest   5cf8fd909c6c  2 days ago     274.3 MB
ubuntu                    trusty   5cf8fd909c6c  2 days ago     274.3 MB
ubuntu                    13.04    7656cbf56a8c  2 days ago     169.4 MB
ubuntu                    raring   7656cbf56a8c  2 days ago     169.4 MB
ubuntu                    12.04    cc0067db4f11  2 days ago     210.1 MB
ubuntu                    precise  cc0067db4f11  2 days ago     210.1 MB

It's also possible to see the history of this newly-created image, showing what changes it is comprised of:

$ docker history capttofu/ubuntu_example
IMAGE               CREATED             CREATED BY                                      SIZE
f54ee24a549a        13 minutes ago      /bin/bash                                       0 B
5cf8fd909c6c        2 days ago          /bin/sh -c apt-get update && apt-get install    81.61 MB
e497c7c1bfbb        2 days ago          /bin/sh -c sed -i 's/^#\s*\(deb.*universe\)$/   1.903 kB
7baf0ef6f14a        2 days ago          /bin/sh -c echo '#!/bin/sh' > /usr/sbin/polic   194.5 kB
35f6dd4dd141        2 days ago          /bin/sh -c #(nop) ADD file:8162d8bad0054f4038   192.5 MB
511136ea3c5a        11 months ago

Pushing up your image

Once an image is built, a way to think of it is that it is ready for sharing. It can be pushed to the public Docker image registry:

$ docker push capttofu/ubuntu_example
$ docker push capttofu/ubuntu_example
The push refers to a repository [capttofu/ubuntu_example] (len: 1)
Sending image list
Pushing repository capttofu/ubuntu_example (1 tags)
Image 511136ea3c5a already pushed, skipping
Image 35f6dd4dd141 already pushed, skipping
Image 7baf0ef6f14a already pushed, skipping
Image e497c7c1bfbb already pushed, skipping
Image 5cf8fd909c6c already pushed, skipping
f54ee24a549a: Image successfully pushed
Pushing tag for rev [f54ee24a549a] on {}

Building images using a Dockerfile

While it is certainly easy to launch a base image, modify it and then commit the changes, it's not something tenable for the long-term purposes of automation and everyday work. This is where using a "Dockerfile" is the solution. A "Dockerfile" is another great example on the utility of Docker and analogous to a Makefile. It is a simple text file that describes a set of instructions for how an image is to be built.

For instance, below is a simple Dockerfile that can be used to install Percona XtraDB Cluster:

# Percona XtraDB Cluster Dockerfile
# VERSION    0.0.1
FROM ubuntu:13.04
MAINTAINER Patrick aka CaptTofu Galbraith ,

# Add the Percona apt repository
RUN apt-key adv --keyserver --recv-keys 1C4CBDCDCD2EFD2A
ADD percona.list /etc/apt/sources.list.d/percona.list

# Update distribution
RUN apt-get update \
      && apt-get upgrade -y \
      && apt-get clean

# Install ssh, vim and PXC packages
RUN DEBIAN_FRONTEND=noninteractive apt-get install -y ssh vim percona-xtradb-cluster-client-5.6 percona-xtradb-cluster-server-5.6

# set up ssh and authorized keys so one can ssh into this system
RUN mkdir /var/run/sshd
RUN mkdir /root/.ssh
RUN chmod 700 /root/.ssh
ADD /root/.ssh/authorized_keys
RUN chown -R root:root /root/.ssh

# add entrypoint script used to start processes
ADD /usr/local/sbin/

# Expose SSH and MySQL ports
EXPOSE 22 3306 4444 4567 9200

# Set the entrypoint to
ENTRYPOINT ["/usr/local/sbin/"]

As can be seen, there are several instructions in this Dockerfile that one would commonly use. The RUN instruction is used to set up apt repositories and update the system. The ADD instruction adds a file to the specific location and name, in this case a script used as an ENTRYPOINT instruction, which is what the container be executed as. The EXPOSE instruction is used to inform docker which ports the container will listen on: in this case ports needed for ssh and Galera replication and automatic failover setup.

Do take note that since the ENTRYPOINT instruction is used, this container will be run differently than the previous example above. This will be explained later in this post.

Building an image using a Dockerfile

Building the image is painfully simple:

$ docker build -t "capttofu/pxc" /my/path/to/pxc/dockerfile/
Uploading context 6.656 kB
Uploading context
Step 0 : FROM ubuntu
 ---> 5cf8fd909c6c
Step 1 : MAINTAINER Patrick aka CaptTofu Galbraith ,
 ---> Running in 3d950f3de14f
 ---> 5767d785bbcd
Removing intermediate container 3d950f3de14f
Step 2 : RUN apt-key adv --keyserver --recv-keys 1C4CBDCDCD2EFD2A
 ---> Running in e59f5947a662
Executing: gpg --ignore-time-conflict --no-options --no-default-keyring --homedir /tmp/tmp.UqCbfHSVSa --no-auto-check-trustdb --trust-model always --keyring /etc/apt/trusted.gpg --primary-keyring /etc/apt/trusted.gpg --keyserver --recv-keys 1C4CBDCDCD2EFD2A
gpg: requesting key CD2EFD2A from hkp server
gpg: key CD2EFD2A: public key "Percona MySQL Development Team <>" imported
gpg: Total number processed: 1
gpg:               imported: 1
 ---> bfee11a71dae
Removing intermediate container e59f5947a662
Step 3 : ADD percona.list /etc/apt/sources.list.d/percona.list
 ---> fb16cc14e7d1
Removing intermediate container 79beea842c16
Step 4 : RUN apt-get update      && apt-get upgrade -y      && apt-get clean
 ---> Running in 8cadef9f201f
Ign trusty InRelease
Get:1 raring InRelease [15.3 kB]
< ... snip ... many lines of apt-get update output .. >
Processing triggers for ureadahead (0.100.0-16) ...
Processing triggers for initramfs-tools (0.103ubuntu4.1) ...
 ---> f439c7087235
Removing intermediate container 8cadef9f201f
Step 5 : RUN DEBIAN_FRONTEND=noninteractive apt-get install -y libterm-readline-gnu-perl ssh vim percona-xtradb-cluster-client-5.6 percona-xtradb-cluster-server-5.6
 ---> Running in 21b9a5ebcac5
Reading package lists...
Building dependency tree...
Reading state information...
The following extra packages will be installed:
  ca-certificates iproute krb5-locales libaio1 libck-connector0
< ... snip ... many lines of apt-get install output ... >
Processing triggers for libc-bin (2.19-0ubuntu6) ...
Processing triggers for ca-certificates (20130906ubuntu2) ...
Updating certificates in /etc/ssl/certs... 164 added, 0 removed; done.
Running hooks in /etc/ca-certificates/update.d....done.
Processing triggers for ureadahead (0.100.0-16) ...
 ---> 096ffef048d9
Removing intermediate container 21b9a5ebcac5
Step 6 : RUN mkdir /var/run/sshd
 ---> Running in 3a4fcf5d7f29
 ---> 072bb5f6ba3b
Removing intermediate container 3a4fcf5d7f29
Step 7 : RUN mkdir /root/.ssh
 ---> Running in 022b27983f27
 ---> 64fef47cb18b
Removing intermediate container 022b27983f27
Step 8 : RUN chmod 700 /root/.ssh
 ---> Running in 49aedf1a4343
 ---> 8fc7cf6bd075
Removing intermediate container 49aedf1a4343
Step 9 : ADD /root/.ssh/authorized_keys
 ---> a7e9f80410b5
Removing intermediate container 4d44374c82fe
Step 10 : RUN chown -R root:root /root/.ssh
 ---> Running in 424c151e8281
 ---> 6edc721b9876
Removing intermediate container 424c151e8281
Step 11 : ADD /usr/local/sbin/
 ---> d9f9a1a92d99
Removing intermediate container 26f08f5661e3
Step 12 : EXPOSE 22 3306 4444 4567 9200
 ---> Running in f873a3b78049
 ---> cfb3e7275caa
Removing intermediate container f873a3b78049
Step 13 : ENTRYPOINT ["/usr/local/sbin/"]
 ---> Running in ec5b19d1bbd0
 ---> 9a9aa0bb48b8
Removing intermediate container ec5b19d1bbd0
Successfully built 289923fa1196

After this build command has completed, there will exist an image tagged as capttofu/pxc to use. The docker images command can be run in order to see it.

Building images with running services

More pragmatic examples are building containers with running services, particularly sshd, that allow one to access a running container either through SSH, a database connection (think PaaS applications, DBaaS, etc) or any other method of interaction with a service that a client would make. To do this, one needs to ensure the service in question is started when the container runs. As seen in previous exemples in this post, a shell script called "" is used.

An example of a simple entrypoint script that starts ssh is:


/usr/sbin/sshd -D

If this script is added to the container, set with appropriate permissions, it will run when the container is launched, allowing one to ssh into the container. Note that other commands can be added to this script. For instance, in the case of the Percona XtraDB Cluster container, "service start mysql" is also added.

To launch images with entrypoints, docker run is issued differently, using the -d flag, which means to run detached. Two examples are:

$ docker run -d --name=pxc1 capttofu/pxc

$ docker run -d --name pxc1 289923

(Note: In the example above, the second example used only the first 6 characters of the image ID. As long as the string used in any Docker command that pertains to an ID is unique, it will work. Had there been another image ID with the same 6 characters, it would have been ambiguous and failed.)

The next thing that needs to be done is to ensure the container is running:

$ docker ps

CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 59b7425cd5cf 289923fa1196 "/bin/sh -c "/usr/lo 2 seconds ago Up 1 seconds 22/tcp, 3306/tcp, 4444/tcp, 4567/tcp, 9200/tcp pxc1

Then find out the IP address of the container by grepping the output of docker inspect:

$ docker inspect pxc1|grep IPA
      "IPAddress": "",

Now one can ssh into the container!

$ ssh root@

root@59b7425cd5cf:~# ps aux
root  1    0.0  0.0  4444    628 ?        Ss   11:25   0:00 /bin/sh -c "/usr/local/sbin/" /bin/sh -c #(n
root  11   0.0  0.0  17908   1436 ?       S    11:25   0:00 /bin/bash /usr/local/sbin/
root  12   0.0  0.0  52256   2876 ?       S    11:25   0:00 /usr/sbin/sshd -D
root  13   0.0  0.0  75648   3720 ?       Ss   11:25   0:00 sshd: root@pts/0
root  25   0.0  0.0  18168   2080 pts/0   Ss   11:25   0:00 -bash
root  72   0.0  0.0  4444    776 pts/0    S    11:33   0:00 /bin/sh /usr/bin/mysqld_safe
mysql 145  2.3  8.9  1143332 461120 pts/0 Sl   11:33   0:00 /usr/sbin/mysqld --basedir=/usr --datadir=/var/lib/mysql
root  186  0.0  0.0  15572   1196 pts/0   R+   11:33   0:00 ps aux

See above the output of ps showing running processes in the container. This provides a good example of how when within a container, it's apparent how the numer of processes is limited to only the application in question (and SSH).

The author sees this as an excellent example of how this is great technology works, and furthermore could have used something like this years ago to solve numerous problems!


As promised earlier in this post, it's worth explaining what ENTRYPOINT and CMD instructions are and what the difference is between the two. ENTRYPOINT is essentially the binary that you are running the container as. CMD is the same thing, except that ENTRYPOINT is more difficult to override and you have to explicitely use --entrypoint command line option to override the executable ENTRYPOINT specifies whereas with CMD, you simply provide that as the CMD argument on the command line.

An example provides a better explaination.

A Dockerfile defining an entrypoint using some arbitrary command:

ENTRYPOINT: ["/usr/bin/mycommand", "arg1", "argN"]

If a container using the image myimage was run, it would execute as /usr/bin/mycommand arg1 argN when run as:

$ docker run -d --name=container1 myimage

If the need was to run the container interactively, perhaps the developer also makes changes to the image manually, it would (have to be) run this as:

$ docker run -it --name=container1 --entrypoint /bin/bash myimage "-"

If CMD had instead been used in the Dockerfile:

CMD: ["/usr/bin/mycommand", "arg1", "argN"]

The same is true in that the container would run the binary /usr/bin/mycommand arg1 argN. If one needed to run the container interactively, simply providing the binary would work:

$ docker run -it --name=container1 myimage /bin/bash

In other words, if you think you are going to iteratively develop and make changes to a particular image that you plan to run backgrounded, CMD may work best for you since it's more straighforward to override. If you think you have the image nailed down and don't plan to run the container interactily, use ENTRYPOINT.

About ports exposure and publishing

The next bit of information to cover pertains to ports and how they are exposed. In an upcoming post detailing work the author completed with Docker and Ansible, the output from a [demonstration][moonshot] that the author gave during the recent Ansible Fest 2014 in New York is provided demonstrating managing Docker across 45 Moonshot cartridges (in other words, bare-metal servers). To be able to reach Docker containers from external hosts, the setting shown in the previous post whereby the command option --ip is used to specify that containers, when launched, will run bound to The other required piece of this functionality requires having Docker bind a given port of the container to a port on the Docker host. For instance, one will want to be able to SSH into containers from an external host. An example would be to ssh from HostA to any on of the containers running on HostB, each running sshd on port 22. How would one ssh to any one of these containers and know what port to use to reach the correct container? The following example shows how this is done.

First, one can specify publish ports with the -p flag:

$ docker run -d -p 48522:22 --name pxc1 289923

Or, allow Docker to publish all exposed ports, automatically chosing the port:

$ docker run -d -P --name pxc2 289923

To view the ports mapped on the host to the Docker ports for port 22:

$ docker ps
CONTAINER ID  IMAGE        COMMAND               CREATED        STATUS        PORTS                                                                                                                       NAMES
ddcceffb801a  289923fa1196 "/bin/sh -c "/usr/lo  2 seconds ago  Up 2 seconds>22/tcp,>3306/tcp,>4444/tcp,>4567/tcp,>9200/tcp   pxc2
05253564fe90  289923fa1196 "/bin/sh -c "/usr/lo  22 seconds ago Up 22 seconds 3306/tcp, 4444/tcp, 4567/tcp, 9200/tcp,>22/tcp                                                               pxc1

As one can see, one would use port 48522 on the host OS to connect to the container pxc1 and port 49180 on the container pxc2.

docker inspect can also be used, though is much more verbose. The port mapping found within several entries of the output for the container in question.

(Note: output formatted and reduced for instructional purposes)

$ docker inspect pxc1
  "ID": "05253564fe90ae9e473f1ac34104e61f53c5db86d24e21266c8fefb6386477bb",
  "NetworkSettings": {
      "IPAddress": "",
      "Ports": {
          "22/tcp": [
                  "HostIp": "",
                  "HostPort": "48522"
          "3306/tcp": null,
          "4444/tcp": null,
          "4567/tcp": null,
          "9200/tcp": null
  "HostConfig": {
      "PortBindings": {
          "22/tcp": [
                  "HostIp": "",
                  "HostPort": "48522"
          "3306/tcp": null,
          "4444/tcp": null,
          "4567/tcp": null,
          "9200/tcp": null


Finally, there are several ways to stop containers, as well as perform some good house-keeping:

The following command docker stop sends a SIGTERM, and after a grace period, SIGKILL:

$ docker stop <container>

docker kill Sends a SIGKILL:

$ docker kill <container>

The above two, you can restart with

$ docker start <container>

If it is found that by running docker ps -a that there are a lot of stopped containers that are no longer needed and need to clean up to reclaim disk space on the host, docker rm is run:

$ docker rm <container>

If laziness is desired (and caveat emptor-- the author is not responsible!)

$ docker rm $(docker ps -a -q)

Also, one may wish to clean up old containers.

$ docker rmi <image>


This post was meant provide the reader with information and practical examples on how to use Docker. At this point, the reader should feel confident that using Docker is very simple and have a good basis for beginning to build their own images and run containers to do useful work!

The next post will feature using Ansible to manage Docker: build images and run containers, using some useful Ansible Docker modules and plugins.

comments powered by Disqus