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 <email@example.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 firstname.lastname@example.org 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.
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.shof 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.
Time to develop your own app processes now – you could install in the new app container xo-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!