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 CoreOS, Serf, Flynn, 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:
- Install VirtualBox
- Install Vagrant
- Install docker-osx (I can’t yet get the new version, boot2docker working for me even though docker-osx is supposedly deprecated, docker-osx works great)
docker-osx will create a VirtualBox virtual machine running docker automatically and allow you to run docker on your OS X command line as if it were running locally (proxied transparently to your VirtualBox docker).
$ export DOCKER_HOST="tcp://172.16.42.43:4243" $ echo 'export DOCKER_HOST="tcp://172.16.42.43:4243"' >> ~/.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
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,
$ 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
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 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
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/mysql images to a private Docker registry. We will show you how to run your own registry in an upcoming tutorial.
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.