Docker is a platform that allows you to deploy any kind of app in a uniform way. Dokku builds on this to create a full PaaS. It allows you to simply push your code repository to your server and let Dokku build and deploy it for you, automatically.

While Dokku automatic builds are powerful and awesome, sometimes you want a bit more control over how your app is deployed. For instance, you may want to deploy your app in one container and link it with another container running your database. Or you want to use one of the vast array of Docker images available in the Docker registry.

Dokku has a number of community plugins that go a long way toward making such a scenario possible. Still, it doesn’t feel like the right tool for the job. After all, one of the greatest advantages of Docker is that you can run similar or even identical containers on your production server and on your development environment, ensuring painless deploying. Dokku means giving up control of how your app will be deployed. Which is great for simple applications or for testing, but for serious production use you’ll probably want that control back.

Let’s say I have a modest number of projects I want to deploy, so I figure a single server should be enough. I want to use pre-defined Docker images to deploy my projects in production on this server. At the same time I also want to have Dokku running on it so I can quickly push some new code to it to see if it will work. It’s taken some research to find how to effectively use Docker and Dokku on a single server. Here’s what you do.

Setting up the server

I created a VPS on DigitalOcean  using their pre-defined Dokku image. It turned out to be very up-to-date, with the latest stable versions of Docker and Dokku running. If you’re running your own server, you can install the latest Docker like this, and install Dokku using the install instructions.

Deploying a simple app with Dokku

After installing Dokku, you also need to specify on which domain you want Dokku to deploy new apps. Write the domain name, e.g. mydomain.com, to /home/dokku/VHOST. Now that Dokku is up and running, we need to register the SSH key of our development machine so that we can push our code to Dokku. On your development machine run this:

cat .ssh/id_rsa.pub| ssh root@mydomain.com sshcommand acl-add dokku myname

( The myname is to keep track of that key in case you want to delete it later. )

Now we’re ready to use Dokku. To try it out, check out a sample node.js project on your development machine and then push it to Dokku:

git clone git@github.com:heroku/node-js-sample.git
cd node-js-sample
git remote add dokku dokku@mydomain.com:test
git push dokku master

Now you should see something like this:

Counting objects: 381, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (308/308), done.
Writing objects: 100% (381/381), 210.18 KiB | 0 bytes/s, done.
Total 381 (delta 49), reused 373 (delta 44)
-----> Cleaning up ...
-----> Building test ...
-----> Adding BUILD_ENV to build environment...
-----> Node.js app detected
-----> Requested node range: 0.10.x
-----> Resolved node version: 0.10.35
-----> Downloading and installing node
-----> Exporting config vars to environment
-----> Installing dependencies

...

-----> Running post-deploy
-----> Creating new /home/dokku/test/VHOST...
-----> Configuring test.mydomain.com...
-----> Creating http nginx.conf
-----> Running nginx-pre-reload
	   Reloading nginx
=====> Application deployed:
	   http://test.mydomain.com

And, like it says right there, your application is now deployed to http://test.mydomain.com!

Deploying a Wordpress blog with Docker

Alright, so now we’ve seen how Dokku can build our apps for us. Now let’s try deploying a pre-existing Docker image: we’ll deploy a blog using the official Wordpress Docker image. But before you can set up a new blog, you’ll first need to set up a MySQL database server. Here is how I did it:

docker run --name mysql --restart=always \
	   -e MYSQL_ROOT_PASSWORD=some-secret-string -d mariadb

The --restart=always will ensure that the Docker daemon starts up the container again after an error or reboot. The Wordpress container can set up its own database if you give it root access to the DB server, but I wanted to try doing it myself. Rather than running a mysql client in another container, I installed it directly on my server:

apt-get install -qqy mysql-client
mysql -h`docker inspect --format "{{ .NetworkSettings.IPAddress }}" mysql` \
	  -uroot -p

The code between backticks retrieves the IP address assigned to my db server container so I can connect to it. \EDIT: I’ve found a more elegant way to do this Now I can set up a database for my new blog by hand:

CREATE DATABASE myblog;
CREATE USER 'myblog'@'%' IDENTIFIED BY 'another-password';
GRANT ALL ON myblog.* TO 'myblog'@'%';
FLUSH PRIVILEGES;

Now we’re ready to deploy our Wordpress blog:

docker run --name myblog --link mysql:mysql \
	   -e WORDPRESS_DB_USER=myblog -e WORDPRESS_DB_PASSWORD=another-password \
	   -e WORDPRESS_DB_NAME=myblog -e VIRTUAL_HOST=blog.mydomain.com \
	   --restart=always -d wordpress

Now our blog container is running, as we can see by running docker ps. However, we can only access its port 80 directly from the server. The VIRTUAL_HOST env shows where we really want to access our new blog, but it’s not working yet.

Reverse-proxy with nginx using docker-gen

We should already have nginx running on our server since Dokku depends on it. It wouldn’t be hard to find the IP address of our blog container and have nginx act as a reverse proxy for it, using our domain name. The problem is that if our blog container is restarted, e.g. after a reboot of our server, its IP address may have changed and we have to update our nginx config by hand. Fortunately, somebody has been here before us and has created a solution: docker-gen. It’s a tool that automatically builds and updates config files for all running containers. Let’s install this wonderful tool:

cd /tmp
wget https://github.com/jwilder/docker-gen/releases/download/0.3.6/docker-gen-linux-amd64-0.3.6.tar.gz
tar xzf docker-gen-linux-amd64-0.3.6.tar.gz
mv docker-gen /etc/nginx/docker-gen

Now we have to create a template for the nginx config we want to generate. I based mine on jwilder’s nginx-proxy. Write your config to /etc/nginx/docker.template. Now we can run docker-gen as a single command. But if we want it to run automatically whenever our server reboots, then we’ll have to install it as a service. To do so, write this script in /etc/nginx/docker-gen-service:

#!/bin/bash
/etc/nginx/docker-gen -only-exposed -watch -notify "service nginx reload" \
	  /etc/nginx/docker.template /etc/nginx/sites-enabled/docker_containers

And make it executable: chmod +x /etc/nginx/docker-gen-service. Now we’ll write some upstart config to /etc/init/docker-nginx.conf:

# docker-nginx - Nginx config generator for Docker containers

description "Nginx config generator for Docker containers"
author "Somebody <somebody@example.com>"

# When to start the service
start on filesystem and started docker

# When to stop the service
stop on runlevel [016]

# Automatically restart process if crashed
respawn

# Send output to logfile
console log

# Run before process
pre-start script
    [ -d /etc/nginx/certs ] || mkdir -p /etc/nginx/certs
end script

# Start the process
exec /etc/nginx/docker-gen-service

And now we start our new service: initctl start docker-nginx. Now docker-gen is keeping an eye on all our Docker containers, and updating our nginx config to match. To do this, it looks at the VIRTUAL_HOST env for each container. Remember that for our Wordpress container we specified -e VIRTUAL_HOST=blog.mydomain.com (if you want to assign more than one hostname to a single container, you can use a comma-separated list: VIRTUAL_HOST=blog.a.com,test.b.com). So provided our DNS settings are correct, we should now be able to visit http://blog.mydomain.com and be greeted by the Wordpress configuration screen.


So there you have it: you can now quickly and easily deploy an app using Dokku, while at the same time deploying production apps more precisely with Docker.