Making Docker Images

To build your own Docker image, you need to create a Dockerfile. Here's an example:

# This Dockerfile is BROKEN: it's an EXAMPLE, read on
FROM debian
MAINTAINER gorr@example.com
EXPOSE 80
RUN echo "APT::Install-Recommends \"false\";" >  /etc/apt/apt.conf.d/02norecommendssuggests
RUN echo "APT::Install-Suggests   \"false\";" >> /etc/apt/apt.conf.d/02norecommendssuggests
RUN apt-get update
RUN apt-get -y install nginx
COPY html /usr/share/html

To construct your Docker image, run docker build -t basicweb . in the folder that contains the Dockerfile. You can simply run docker build . but this results in an anonymous hexadecimal-labeled image: named images are a lot nicer to deal with. The commands in the Dockerfile are reasonably self-evident: FROM says what base image to start with (see my previous docker entry on how to find images in the repository. MAINTAINER isn't required, but is good form. EXPOSE is used to make port 80 available within the Docker infrastructure: making it available to the outside world takes another step that we'll get to shortly. RUN is used to apply changes to the image. Note that apt-get install <package> has to be run with -y because otherwise apt-get gives an interactive prompt that will wait forever. I've also chosen to disable the installation of "recommended" and "suggested" apt packages to reduce the size of the image (this is done before any apt-get commands). And COPY copies files "from the local context" (Dockers' documentation, which I take to mean "within the folder containing the Dockerfile, not parent folders") to the image. In this case, I have a folder full of working HTML files.

This image should build successfully. To run it, use docker run -d -p 80:80 basicweb - but at this point we find out it will immediately exit. As I understand it, this is because Docker containers only continue to run if they have a foreground service. So we add a line a couple lines:

FROM debian
MAINTAINER gorr@example.com
EXPOSE 80
RUN echo "APT::Install-Recommends \"false\";" >  /etc/apt/apt.conf.d/02norecommendssuggests
RUN echo "APT::Install-Suggests   \"false\";" >> /etc/apt/apt.conf.d/02norecommendssuggests
RUN apt-get update
RUN apt-get -y install nginx
RUN echo "\ndaemon off;" >> /etc/nginx/nginx.conf
# source material must be in context, ie. in the current directory, not outside it
# only the contents of a source directory are copied, not the dir itself
COPY html /usr/share/html
CMD nginx

Now the image runs fine and can be seen in the Docker process table with docker ps. But the web page at http://localhost/ is the default Debian Nginx page because I've copied the HTML documents to the wrong location. Let's kill it and start it again with a window into it:

$ docker ps
CONTAINER ID        IMAGE               COMMAND              CREATED             STATUS              PORTS                NAMES
ff48a4074d12        basicweb            "/bin/sh -c nginx"   4 seconds ago       Up 2 seconds        0.0.0.0:80->80/tcp   stoic_albattani
$ docker kill stoic_albattani
stoic_albattani
$ docker run -ti basicweb /bin/bash
root@99d6bf38aab7:/# less /etc/nginx/sites-enabled/default
bash: less: command not found
root@99d6bf38aab7:/# cat /etc/nginx/sites-enabled/default

From examining this file, I find that the default HTML file location is root /var/www/html;. So I corrected the COPY line to COPY html /var/www/html and when run again the container now provides the HTML documents I expect on port 80 of localhost.

Creating Dockerfiles is a slow iterative process of testing and refinement - usually with a lot more cycles than I've shown above. But this was a very simple example.

Here's another mostly complete Dockerfile, this one the result of a previously mentioned learning lab on the subject. You can trace its design by reading the lab notes, in which the instructor carefully has you construct the following file line by line so you understand everything in it:

FROM centos:6
RUN yum install -y httpd
MAINTAINER gorr@example.com
CMD /usr/sbin/apachectl -DFOREGROUND -k start
EXPOSE 80
RUN yum install -y tar bzip2
COPY owncloud-7.0.6.tar.bz2 /var/www/html/
RUN cd /var/www/html/ && tar xvfj owncloud-7.0.6.tar.bz2 && rm -f owncloud-7.0.6.tar.bz2
RUN yum install -y php php-dom php-mbstring php-pdo php-gd
VOLUME /data
COPY config.php /var/www/html/owncloud/config/config.php
RUN chown -R apache:apache /var/www/html/owncloud /data

In this case, you'll need to download the owncloud-7.0.6.tar.bz2 tarball to the same directory as the Dockerfile. You'll need to have a /data/ folder on the machine running the Docker instance. You'll need a config.php for OwnCloud, which is most easily constructed by running OwnCloud in the Docker instance, doing the base configuration, and then copying out the resulting file (docker cp ecstatic_tesla:/var/www/html/owncloud/config/config.php . to copy a file out of a running container). I also suspect that the installation hauls in a database engine and then doesn't put a master password on it: as it's in a container this isn't disastrous - but it's far from best practice.

Managing Docker Images

It's worth checking occasionally how much space your Docker Images occupy:

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
basicweb            latest              e15fb217de1d        10 hours ago        176.1 MB
<none>              <none>              f8db3b4002fb        11 hours ago        176.1 MB
<none>              <none>              b839fbee95cb        11 hours ago        176.1 MB
<none>              <none>              339576126c97        11 hours ago        176.1 MB
owncloud            latest              90f5388c296d        6 days ago          352.6 MB
<none>              <none>              03793ab59c43        6 days ago          185.2 MB
<none>              <none>              15ba0a3ed852        6 days ago          125.1 MB
debian              latest              031143c1c662        12 days ago         125.1 MB
hello-world         latest              c54a2cc56cbb        10 weeks ago        1.848 kB

As it turns out, this is somewhat deceptive. The unnamed ones are (I think - take this with a grain of salt) actually intermediate mis-steps, slices of Docker image file systems that have been abandoned. So 339576126c97 was a part of what eventually became basicweb, and it would occupy 176MB if I were using that entire Docker image. Still, it doesn't hurt to clean up - especially if you've completely abandoned a line of development.

$ docker rmi 339576126c97
Error response from daemon: conflict: unable to delete 339576126c97 (must be forced) - image is being used by stopped container 4d989f6204eb
$ docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                      PORTS               NAMES
f26b271cfd33        basicweb            "/bin/sh -c nginx"       10 hours ago        Exited (137) 10 hours ago                       trusting_khorana
4e0263d585da        basicweb            "-d nginx"               10 hours ago        Created                                         distracted_lumiere
99d6bf38aab7        f8db3b4002fb        "/bin/bash"              11 hours ago        Exited (0) 10 hours ago                         ecstatic_lamport
ff48a4074d12        f8db3b4002fb        "/bin/sh -c nginx"       11 hours ago        Exited (137) 11 hours ago                       stoic_albattani
6dc0c2bc27d6        430a5a61f2cd        "-d nginx"               11 hours ago        Created                                         drunk_davinci
9eba61da1cd9        b839fbee95cb        "/bin/sh -c nginx"       11 hours ago        Exited (0) 11 hours ago                         silly_archimedes
4d989f6204eb        339576126c97        "/bin/sh -c 'service "   11 hours ago        Exited (0) 11 hours ago                         elated_poitras
90d80b110813        430a5a61f2cd        "/bin/bash"              11 hours ago        Exited (0) 11 hours ago                         sharp_swanson
b06e441837d9        430a5a61f2cd        "/bin/bash"              11 hours ago        Exited (0) 11 hours ago                         zen_roentgen
$ docker rm zen_roentgen
zen_roentgen
$ docker rm sharp_swanson elated_poitras silly_archimedes drunk_davinci stoic_albattani ecstatic_lamport distracted_lumiere trusting_khorana
sharp_swanson
elated_poitras
silly_archimedes
drunk_davinci
stoic_albattani
ecstatic_lamport
distracted_lumiere
trusting_khorana
$ docker rmi 339576126c97
Deleted: sha256:339576126c97838b9d7d2c891ffb60087ae956e4ed9dee4ca5d3944210308e30
$ docker rmi 15ba0a3ed852 03793ab59c43 b839fbee95cb f8db3b4002fb
Deleted: sha256:15ba0a3ed852ce3a53a0c915dd1970977f5509a772cb3c343120bd92a9f1b752
Deleted: sha256:52d9be9d22771383b0bce52176cbe4aeb1be18ff963328db81e246c78f160003
Deleted: sha256:03793ab59c43b9cb321f9fdcd736921a9aef1894a7c10fc7e92c8a6a43bdb98b
Deleted: sha256:7e46d22e7f2a12208576a86ba8824f9cde05afbbeac8594d836758674f650fc9
Deleted: sha256:b0323516ad75942dbac79a348b38713dbffcf68e4263b0da22cff59c21e2f94f
Deleted: sha256:a9887ef5524e5c4df5e48a3290f72924d4bc1520c334592bc94054faceb57f0f
Deleted: sha256:379e3fed830e573dc497159e8fdc197a937fbaa461b0ff0f4435e1e07ee2fad7
Deleted: sha256:985b95036aa499a0667194367f4f2bf13fe8002adde01ead02ac6bce650e521c
Deleted: sha256:6c354589940d2e890204bcfb0ed166134eeb193e14ed985382ad8f6ed9612911
Deleted: sha256:e6796d0d00581e5a42077815aff604b43b3070ebcf70110d2ff6fd470dcb784e
Deleted: sha256:270d88ba6382496182d6876abc9de8730616856b68c50b0fc6ae4e1f5e74a62f
Deleted: sha256:ab98df599990a30aed499f8d2c4f9fcbbe29a300ffe111eeb9a73be3cfc5f344
Deleted: sha256:b839fbee95cbcb1a5d87cfd588fa14f724540c0d5a824687474a8ae10e6c8037
Deleted: sha256:430a5a61f2cda448707341491cda6107b096f742597bb5c20bf769714cc8bd9c
Deleted: sha256:7c3f524c58452abc5dbdffe28e3222e290da523d0a9820694e664e167e989b6e
Deleted: sha256:f8db3b4002fb377038c96347320d9dcc6bc118ecb0897755395257d57cf56916
Deleted: sha256:a81d2b379ae36d2038de9bbe32e5437cfd9ccd97508e1efd85d77b7ff7e48bbd
Deleted: sha256:b2d5eb9f30c73645b9af48b5ebe5cb6c9f8a399d36234528f776dd8ed288e347
$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
basicweb            latest              e15fb217de1d        11 hours ago        176.1 MB
owncloud            latest              90f5388c296d        6 days ago          352.6 MB
debian              latest              031143c1c662        12 days ago         125.1 MB
hello-world         latest              c54a2cc56cbb        10 weeks ago        1.848 kB

This only cleared about 100MB of space. But it feels tidier. Notice the distinction between docker rm <container> and docker rmi <image>.