This post was written by Markus Stefanko

Breaking down a Dockerfile

A while ago after speaking with the great guys over at Rackspace, we discovered Docker, and have not looked back since.

Docker allows you to create server instances in the same way you would create APPs in your application. What does that mean? It means you can run your own little Heroku or SaaS platform easily on dedicated machines or your cloud instances without a lot of effort.

Docker adds deployment automation on top of something called LXC – short for LinuX Containers. This way you could run on one machine multiple containers with different operating systems, different app structures; but still sharing the necessary O/S resources to run; so you could use many more virtual machines on one server than with traditional Virtual Images.

An example Dockerfile

Let’s take this Dockerfile as an example – taken from John Fink’s WordPress Docker :

FROM ubuntu:latest
MAINTAINER John Fink <john.fink@gmail.com>
RUN apt-get update # Fri Dec 13 12:24:34 EST 2013
RUN apt-get -y upgrade
RUN DEBIAN_FRONTEND=noninteractive apt-get -y install mysql-client mysql-server apache2 libapache2-mod-php5 pwgen python-setuptools vim-tiny php5-mysql openssh-server sudo php5-ldap
RUN easy_install supervisor
ADD ./start.sh /start.sh
ADD ./foreground.sh /etc/apache2/foreground.sh
ADD ./supervisord.conf /etc/supervisord.conf
RUN echo %sudo        ALL=NOPASSWD: ALL >> /etc/sudoers
RUN rm -rf /var/www/
ADD http://wordpress.org/latest.tar.gz /wordpress.tar.gz
RUN tar xvzf /wordpress.tar.gz 
RUN mv /wordpress /var/www/
RUN chown -R www-data:www-data /var/www/
RUN chmod 755 /start.sh
RUN chmod 755 /etc/apache2/foreground.sh
RUN mkdir /var/log/supervisor/
RUN mkdir /var/run/sshd
EXPOSE 80
EXPOSE 22
CMD ["/bin/bash", "/start.sh"]

Don’t worry, it’s not really confusing; let’s break it down into parts :

  • Get the latest Ubuntu Docker container, to run commands on top of it
  • Update APT and upgrade our current Ubuntu to the latest version
  • Install MySQL, Apache, PHP5, Python Setuptools and OpenSSH
  • Install supervisor through setuptools, to control process states later on
  • ADD a few files we want to RUN later on
  • RUN command to add a SUDO entry to /etc/sudoers
  • Remove /var/www as we want a fresh WordPress in there
  • Download WordPress, extract it, and assign it to the Apache user www-data
  • CHMOD the correct permissions to the shell scripts we added earlier
  • Create a few directories we’ll need later
  • Expose the ports 80 ( Webserver) and 22 ( SSH )
  • And finally, run /bin/bash /start.sh which contains runtime configuration instructions

Now what’s so great with that? We can do that easily with other Virtual Image environments?

The beauty is, that each of those lines ends up in the Docker cache. So if you would like to build a few Docker instances with the same empty WordPress install, it would happen within seconds after the first run.

The cache would stay in Docker, until you change one of the Dockerfile lines, which will invalidate the cache for any lines below it. So let’s say you’d change ADD ./start.sh /start.sh, to ADD /start.sh /startscript.sh, any line below would be executed again when building a new image; however any line above the affected line would be still read from cache – handy, as we won’t need to apt-get upgrade every time we launch a new instance!

If you would like to start a new container in daemon mode – that is – it should stay up after being started with run, you would add supervisord -n as the last command; or as is in this case – it’s added to the /start.sh script at the end. This will keep the instance running, as supervisord will keep on running in non-daemon mode and not terminate the instance until you stop it yourself.

Ok, so we have the instance now, what now?

You can easily inherit this instance now, and use it as your base for your WordPress applications. Let’s say you’d build this Docker instance with the command docker build -rm -t yourname/wordpress, you can now :
* push that version to the public, or your own Docker repository
* create a new Docker container which will continue from where this instance was left

Imagine building an instance called yourname/myapp with this Dockerfile :

FROM yourname/wordpress
MAINTAINER Your Name your@email.com

ADD ./start_app.sh /start_app.sh

## WP install script starts now
CMD ["/bin/bash", "/start_app.sh"]

This will create a new docker container, which has everything from our previous example already inherited! So in /start_app.sh we’d define to run supervisord -n and we have a running container within less than a second, as it’s starting where the built one ended.

Caveats

This is just an example, please remember that in the first Dockerfile we run a script called start.sh with CMD, which means that it runs when you run the docker instance from the created image – not at build time. You would want to move that from CMD to something like RUN sudo /bin/bash /start.sh to have it executed – and cached – during build time.

Second caveat are passwords, currently the start.sh creates the passwords for the first build in the start.sh. If you would inherit from that docker container, you would inherit essentially the exactly same password for all containers created from it. Solution?

  • Change passwords in the start_app.sh of your new container
  • Remove passwords where you can ( SSH authentication by PEM )
  • or move the password generating code to your freshest container – the app – instead

Build & Run

After building our app instance with docker build -rm -t yourname/myapp ., we can run it with docker run -name myapp1 -d -p 10080:80 -p 10022:22 yourname/myapp.

Now you have a running instance which inherited Apache, PHP5, MySQL and WordPress, listening for http requests on port 10080, and for your SSH connections on port 10022.

We’d monitor if everything went fine with docker logs -f myapp1, and we have our own little self-contained docker wordpress instance running.

Going further

Time to develop your own app processes now – you could install in the new app container wp-cli, and automatically install the necessary plugins for the app we’d like, or pull themes and plugins from our private or public git repositories.

Committing the image

As you want to use the images on multiple servers, you’d want to push it when it’s ready to your own private repository in your self-hosted registry.

To set up your own docker registry and push your containers to your repository, read Sam Alba’s extensive summary on the topic.

It could be as easy as :

docker tag myapp1 localhost.localdomain:5000/my_repository
docker push localhost.localdomain:5000/my_repository

From there you’d be able to pull the repository on any of your servers, and simply have it up and running within no-time after the initial builds. No more long waiting for virtual machines to boot up when time is crucial!

by Markus Stefanko