Screen Shot 2014-03-17 at 12.25.19 PM

Docker is fundamentally changing the way people do infrastructure in the cloud. However many developers I know are still confused about how to use it to do anything beyond a simple “Hello World” or a WordPress-all-in-one container with everything bundled into a single container, Apache, MySQL, nginx, etc.

The power of Docker is in being able to ship and scale individual components or “containers”. However it is still a bit of a black art about how that works. There are a few awesome projects being started to make it easier to build multi-container apps like CoreOSSerfFlynn, Maestro, Deis, and Dokku. But what if you want to do it without any fancy tools? How do you do it from scratch?

I am not going to say this is the best way, but I would like to walk you through how I recently built a 2-Container app (WordPress in one container and MySQL in the other). In future articles, we will go into more complex setups and walk you through how to use some of the cool Open Source tools out there.

This tutorial will guide you through 5 somewhat easy steps (CTL-C is a new project we are working on to make this kind of tutorial unnecessary, you will be able to stitch together Docker containers in a simple web UI).

Step 1) Setup Docker Locally

First, setup docker on your machine. If you are on a Mac, this is a simple 3-step process:

  1. Install VirtualBox
  2. Install Vagrant
  3. Install boot2docker (see below)

Here is how to install boot2docker which will install Docker on your Mac.

$ brew install boot2docker # if you are not on a Mac, go to https://www.docker.io/gettingstarted/
$ boot2docker init
$ boot2docker up
$ echo 'export DOCKER_HOST=tcp://localhost:4243' >> ~/.bash_profile
$ source ~/.bash_profile
$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

Step 2) Pull an Existing Container to Start With

Now that you have docker running on your development machine, let’s do something interesting. Let’s create your first single-container application.

First, you go to the public Docker Index to find a container you want to use. Let’s search for WordPress. You will find about 30 results, but you only want to use a Trusted Repository.

NOTE: Most of the docker containers in the Docker Index are unsafe to use as-is. You have no idea how they were built, so you can’t be sure there isn’t malware on the images. If you want to use any public Docker images as a starting point, make sure to use a “Trusted Repository” (read more about trusted builds) and make sure to audit it’s Dockerfile before you use it.

One of the good trusted WordPress containers out there is managed by Tutum, a Docker-based hosting company that lets you run Docker containers and provides private docker registries and private docker images. The WordPress container you want to use is called tutum/wordpress and you can use it quickly and easily by doing the following:

$ docker run -d -p 80 tutum/wordpress /run.sh
Unable to find image 'tutum/wordpress' (tag: latest) locally
Pulling repository tutum/wordpress
ef6168ed7674: Download complete 
27cf78414709: Download complete 
[... more of the same ...]
d713116991b8: Download complete 
567a9d28bb372b23232ac2c1b527caf950b2e8095e2b6cbff0abba9e9ec10ec9
$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
567a9d28bb37        tutum/wordpress:latest                       /run.sh                13 seconds ago      Up 12 seconds       0.0.0.0:49163->80/tcp, 3306/tcp          suspicious_heisenberg

Congratulations! You are now running WordPress in a single container with MySQL on the same container. Easy right?

Want to see WordPress running for yourself? Run docker-osx ssh, and then run curl 0.0.0.0:49163/readme.html

Now how do you get MySQL to run in its own container?

Step 3) Modifying an Existing Container

Now that you can use existing “Trusted Images”, let me show you how to modify them. Instead of running in daemon mode with the -d flag, use the interactive and TTY flags (-i -t) and specify bash to get into the container as root.

$ docker run -i -t tutum/wordpress bash
root@dfa24049c264:/# 

Every wonder what /run.sh did in step 2′s run of the docker command? Let’s find out.

root@dfa24049c264:/# cat run.sh
#!/bin/bash
if [ ! -f /.mysql_admin_created ]; then
	/create_mysql_admin_user.sh
fi
exec supervisord -n

Now that we don’t want MySQL running on the same container as WordPress, let’s modify this container to not run MySQL.

root@dfa24049c264:/# rm /etc/supervisor/conf.d/supervisord-mysqld.conf
root@dfa24049c264:/# touch /.mysql_admin_created 

In a new Terminal window, run docker ps again to find the container id of your interactive container.

$ docker ps
CONTAINER ID        IMAGE                                        COMMAND                CREATED             STATUS              PORTS                             NAMES
dfa24049c264        tutum/wordpress:latest                       bash                   9 minutes ago       Up 9 minutes        3306/tcp, 80/tcp                  condescending_albattani            
110631a70e76        tutum/wordpress:latest                       /run.sh                13 minutes ago      Up 13 minutes       0.0.0.0:49163->80/tcp, 3306/tcp   cocky_einstein                     

Now that we have the container id (dfa24049c264) we can commit our changes to the filesystem.

$ docker commit dfa24049c264 myuser/wordpress
852e9ccd1194f5abbcbfb13c61e54a31d5b716b20bec3d114437e824a39e168a

Now we have our own version of tutum’s WordPress with MySQL disabled. Let’s try it out. First we will stop our other containers and then we will start a new one with our newly committed image, myuser/wordpress.

$ docker stop dfa24049c264
$ docker stop 110631a70e76
$ docker run -d -p 80 myuser/wordpress /run.sh
f1bc5c3bd28aac9a9452fdaeafb527b9616e5167797d56654dee625479c8d70c
$ docker ps
CONTAINER ID        IMAGE                                        COMMAND                CREATED             STATUS              PORTS                     NAMES
f1bc5c3bd28a        myuser/wordpress:latest                      /run.sh                7 seconds ago       Up 6 seconds        0.0.0.0:49164->80/tcp     jovial_nobel                       

Notice that the 3306 port does not show as open in the new container when it did in the previous one, but that you can still curl the url curl 0.0.0.0:49164/readme.html

Step 4) Create Your MySQL Container

Just like WordPress, you can get yourself a MySQL container from the Docker Index.

$ docker run -d -p 3306 tutum/mysql /run.sh
63a974062480cdcb93db788dc2ef72cb1f5cbfe2c180b9fdfb1b13c62753c106
$ docker ps
CONTAINER ID        IMAGE                                        COMMAND                CREATED             STATUS              PORTS                     NAMES
63a974062480        tutum/mysql:latest                           /run.sh                18 seconds ago      Up 17 seconds       0.0.0.0:49165->3306/tcp   cranky_babbage                     
f1bc5c3bd28a        myuser/wordpress:latest                      /run.sh                5 minutes ago       Up 5 minutes        0.0.0.0:49164->80/tcp     jovial_nobel                       

Again, let’s go in and check out what run.sh does.

$ docker run -i -t tutum/mysql bash
root@a6fd073d27bb:/# cat run.sh
#!/bin/bash
if [ ! -f /.mysql_admin_created ]; then
	/create_mysql_admin_user.sh
fi
exec supervisord -n

Look familiar? It should, the same tutum folks made this image. We don’t need to remove any components out of this container, but we do want to make sure that the root username and password do not change. If you go back to your OS X terminal, we can find out what username and password were generated and commit the new container locally.

$ docker run -d -p 3306 tutum/mysql /run.sh
63a974062480cdcb93db788dc2ef72cb1f5cbfe2c180b9fdfb1b13c62753c106
$ docker ps
CONTAINER ID        IMAGE                                        COMMAND                CREATED             STATUS              PORTS                     NAMES
63a974062480        tutum/mysql:latest                           /run.sh                18 seconds ago      Up 17 seconds       0.0.0.0:49165->3306/tcp   cranky_babbage                     
f1bc5c3bd28a        myuser/wordpress:latest                      /run.sh                5 minutes ago       Up 5 minutes        0.0.0.0:49164->80/tcp     jovial_nobel                       
$ docker logs 63a974062480
=> Creating MySQL admin user with random password
=> Done!
========================================================================
You can now connect to this MySQL Server using:

    mysql -uadmin -pqa1N76pWAri9 -h -P

Please remember to change the above password as soon as possible!
MySQL user 'root' has no password but only allows local connections
========================================================================
2014-01-20 21:56:10,100 CRIT Supervisor running as root (no user in config file)
2014-01-20 21:56:10,111 WARN Included extra file "/etc/supervisor/conf.d/supervisord-mysqld.conf" during parsing
2014-01-20 21:56:10,330 INFO RPC interface 'supervisor' initialized
2014-01-20 21:56:10,330 WARN cElementTree not installed, using slower XML parser for XML-RPC
2014-01-20 21:56:10,330 CRIT Server 'unix_http_server' running without any HTTP authentication checking
2014-01-20 21:56:10,330 INFO supervisord started with pid 1
2014-01-20 21:56:11,333 INFO spawned: 'mysqld' with pid 385
2014-01-20 21:56:12,536 INFO success: mysqld entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
$ mysql -uadmin -pqa1N76pWAri9 -h 0.0.0.0 -P 49165 -e 'create database wordpress;'
$ docker commit 63a974062480 myuser/mysql
c1c5e63d427cc533325e3870ab9ed439e8bfe2e95c333ec54657210708816bbd

Ok now we know what the username and password are and have created our own container with a static root username and password. Let’s stop the generic container and start our new custom MySQL server container.

$ docker stop 63a974062480
$ docker run -d -p 3306 myuser/mysql /run.sh
$ docker ps
CONTAINER ID        IMAGE                                        COMMAND                CREATED             STATUS              PORTS                     NAMES
5a840e255282        myuser/mysql:latest                          /run.sh                1 seconds ago       Up 1 seconds        0.0.0.0:49166->3306/tcp   angry_bardeen                      
f1bc5c3bd28a        myuser/wordpress:latest                      /run.sh                23 minutes ago      Up 23 minutes       0.0.0.0:49164->80/tcp     jovial_nobel                       

Step 5) Binding Your WordPress Container to your MySQL Container

Congratulations, you have reached the last step! All you have to do now is bind the two containers together. In order for the two containers to know about each other, we will need to link them using the docker -link flag. Before we do that, we will need to make another modification to our WordPress container so that it can pull credentials from environment variables.

$ docker run -i -t myuser/wordpress bash
root@f74cc171d7d5:/# apt-get install curl
root@f74cc171d7d5:/# curl -L https://gist.github.com/cardmagic/8530589/raw/21c5d2f0a4f661531502a705a797e0357d998c86/gistfile1.txt > /app/wp-config.php

Now you go back to another OS X terminal (keeping the bash container running) and commit these changes to the container.

$ docker commit f74cc171d7d5 myuser/wordpress
$ docker stop f74cc171d7d5

Finally, it is time to start the myuser/wordpress container with a special -link flag and the environmental variable for the MySQL DB password.

$ export DB_PASSWORD=qa1N76pWAri9
$ docker ps
CONTAINER ID        IMAGE                                        COMMAND                CREATED             STATUS              PORTS                     NAMES
5a840e255282        myuser/mysql:latest                          /run.sh                21 minutes ago      Up 21 minutes       0.0.0.0:49166->3306/tcp   angry_bardeen                      
$ docker run -e="DB_PASSWORD=$DB_PASSWORD" -link angry_bardeen:db -d -p 80 myuser/wordpress /run.sh
$ docker ps
CONTAINER ID        IMAGE                                        COMMAND                CREATED             STATUS              PORTS                     NAMES
2fb827d3c3a7        myuser/wordpress:latest                      /run.sh                2 seconds ago       Up 1 seconds        0.0.0.0:49167->80/tcp     ecstatic_thompson                    
5a840e255282        myuser/mysql:latest                          /run.sh                22 minutes ago      Up 22 minutes       0.0.0.0:49166->3306/tcp   angry_bardeen,ecstatic_thompson/db   

We now have a 2-container setup that are bound together. To see it in action, go to:

$ curl http://0.0.0.0:49167/wp-admin/install.php

Conclusion

We can now create a 2-container application in Docker without even having to touch a Dockerfile. We can tie the containers together in a loosely coupled way. You can create multiple instances of the WordPress container that all talk to the same MySQL database.

Note that this is not a production environment yet. The MySQL container’s data is ephemeral and we did not cover how to persist it yet (hint: look at the docker run -v flag).

Also note that in production we would want to docker push our myuser/wordpress and myuser/mysql images to a private Docker registry. We will show you how to run your own registry in an upcoming tutorial.

Next Week

In the next installment next week, we will talk about adding an nginx container to load balance and using Serf to automatically make all of the containers aware of each other. To automatically get an email when the next installment comes out, subscribe to our weekly newsletter that has Docker tips and news.

  • Pingback: How To Build A 2-Container App with Docker | CT...

  • bmullan

    You mention that there are a few awesome projects being started to make it easier to build multi-container apps like CoreOS, Serf, Flynn, Maestro, Deis, and Dokku. But you failed to include or mention Canonical’s JuJu – https://juju.ubuntu.com/ JuJu can be configured with one command to use LXC to deploy application services url: https://juju.ubuntu.com/docs/config-local.html I’ve previously deployed an openstack service using juju and LXC.

    • cardmagic

      Not an intentional oversight! Thanks for the addition!

  • Pingback: Auto-Loadbalancing Docker with Fig, Haproxy and Serf | CenturyLink Labs

  • Pingback: My Very Best Ping

  • Wayne Walls

    I think you might need a “-L” switch on that curl command to make sure the redirect works correctly. Might have just been me, just wanted to throw it out there.

  • Bryan Lee

    thanks for the useful tutorial! There’s another here that I found interesting, http://blog.tutum.co/2014/02/06/how-to-build-a-2-container-app-with-docker-and-tutum/

  • http://kevinridgway.com Kevin Ridgway

    Port ’49165′ should be changed to ’49156′ in the ‘mysql -uadmin -pqa1N76pWAri9 -h 0.0.0.0 -P 49165 -e ‘create database wordpress;” command.

    In the curl command you have to add ‘-L’ to correctly handle the redirect that github has on their gist site for that wp-config file.

  • http://dev.svetlyak.ru Alexander Artemenko

    There is a mistake in “Step 2″. Please change “docker-osx ssh” to “boot2docker ssh” and a note that default ssh password is “tcuser”.