Why?

Because it is cool. And when, for any reason, the app needs to be moved to another server; I can just copy the whole docker repository instead of re-deploy the app and migrate the database and copy over the assets and ... you got the idea.

It took me a whole day to get rid of the gotchas. So this post is mainly a reference for the future me. Actually Dokku works out of the box. I did it manully only to get familiar with Docker.

Prerequisites

A server or a VPS. For VPSes Xen and KVM are good. OpenVZ aren't. (I am not sure if docker can be installed on an OpenVZ VPS at all.)

Ubuntu is the best bet since docker is developed on it. The current LTS is 12.04. In one month time 14.04 should be out. But I am going to stick with 12.04 now.

The VPS should be secured before being used. If you are as paranoid as I am here is a tutorial. Normal people can just change ssh port and use a strong password.

Install Docker

The official guide may or may not work, depend on the VPS provider.

First install back-ported kernel.

# install the backported kernel
sudo apt-get update  
sudo apt-get install linux-image-generic-lts-raring linux-headers-generic-lts-raring

# reboot
sudo reboot  

Then check the kernel version.

uname -a  

If 3.8.0 is displayed then it is good to go. But sometimes it still gives you 3.2.0 or smaller numbers in which case we have some more work to do.

Check files in the /boot folder. Here is an example.

$ ls -l /boot
total 43164  
-rw-r--r-- 1 root root   795318 Jul 26  2013 abi-3.2.0-52-virtual
-rw-r--r-- 1 root root   920492 Feb 19 21:52 abi-3.8.0-37-generic
-rw-r--r-- 1 root root   140644 Jul 26  2013 config-3.2.0-52-virtual
-rw-r--r-- 1 root root   154970 Feb 19 21:52 config-3.8.0-37-generic
drwxr-xr-x 2 root root     4096 Mar 23 18:23 grub  
-rw-r--r-- 1 root root  5664991 Mar 22 18:34 initrd.img-3.2.0-52-virtual
-rw-r--r-- 1 root root 19529247 Mar 22 18:36 initrd.img-3.8.0-37-generic
-rw-r--r-- 1 root root   176764 Nov 27  2011 memtest86+.bin
-rw-r--r-- 1 root root   178944 Nov 27  2011 memtest86+_multiboot.bin
-rw------- 1 root root  2892320 Jul 26  2013 System.map-3.2.0-52-virtual
-rw------- 1 root root  3194987 Feb 19 21:52 System.map-3.8.0-37-generic
-rw------- 1 root root  4964752 Jul 26  2013 vmlinuz-3.2.0-52-virtual
-rw------- 1 root root  5458256 Feb 19 21:52 vmlinuz-3.8.0-37-generic

There are two sets of files. One set for kernel 3.2.0 and the other set for kernel 3.8.0 . Now locate the menu.lst file. Mine is in /boot/grub. Replace the file names in this file with the 3.8.0 versions.

Example:

# This is the original menu.lst file

default=0  
timeout=5  
title vmlinuz-3.2.0-52-virtual  
  root (hd0,0)
  kernel /boot/vmlinuz-3.2.0-52-virtual root=/dev/xvda1 console=hvc0 ro
  initrd /boot/initrd.img-3.2.0-52-virtual
# This is the modified menu.lst file

default=0  
timeout=5  
title vmlinuz-3.8.0-37-generic  
  root (hd0,0)
  kernel /boot/vmlinuz-3.8.0-37-generic root=/dev/xvda1 console=hvc0 ro
  initrd /boot/initrd.img-3.8.0-37-generic

Save, cross your fingers, and reboot. Use uname -a to confirm the kernel version is 3.8.0

Now follow the remaining steps in the official guide.

P.S. Do not install Docker on kernel version lower than 3.8.0 . It won't work properly. I tried.

Container provision

Run sudo docker run -i -t ubuntu /bin/bash and do the provision in this docker container. There might be a few gotchas. I ran into these:

Upon the routine apt-get update/upgrade I got this:

Preconfiguring packages ...  
Setting up initscripts (2.88dsf-13.10ubuntu11.1) ...  
mount: permission denied  
dpkg: error processing initscripts (--configure):  
 subprocess installed post-installation script returned error exit status 1
Errors were encountered while processing:  
 initscripts
E: Sub-process /usr/bin/dpkg returned an error code (1)  

I worked around it this way. Edit line 248 of the file /var/lib/dpkg/info/initscripts.postinst. Replace inchroot with true. Then execute dpkg --configure -a.

Can't use service ssh start

With root privilege run mkdir /var/run/sshd then /usr/sbin/sshd . Or use dropbear.

RVM isn't working

It seems Docker needs RVM to be installed as root (I may be wrong here). However a production server doesn't need RVM either. If for some reason Ruby version control is necessary then rbenv works OK.

If rbenv is used with capistrano, the following changes need to be made in config/deploy.rb:

# Do NOT require rvm/capistrano
# require "rvm/capistrano"
require "bundler/capistrano"

# Add these lines to load rbenv
set :default_environment, {  
  'PATH' => "$HOME/.rbenv/shims:$HOME/.rbenv/bin:$PATH"
}

# Rest of the file...

Some sources for Ubuntu 12.04

After provision, exit the container and commit the changes to an image.

Deploy

I like to have an interactive shell open while testing the deployment.

sudo docker run -t -i -p 49167:22 -p 80:80 <image_name> /bin/bash  

Besides the http port, a random high port on the host is mapped to the container's ssh port for deployment as well. That means the config/deploy.rb should have something like this:

ssh_options[:port] = 49167  

Now the app can be deployed as usual. Migrate the database and assets if needed. Don't forget to commit the container to a new Docker image afterwards.

Let's make some assumptions: The Docker image name is my_app_image and unicorn, nginx and PostgreSQL are used to serve the app.

Create Docker image

Run the Docker image with an interactive shell:

sudo docker run -t -i my_app_image /bin/bash  

Create a file for the Docker entrypoint. It can be placed anywhere. I will just use /usr/bin/start.sh.

#!/bin/bash

# For RVM uncomment the next line. (I have not tested this)
# source /etc/profile.d/rvm.sh 
/usr/sbin/sshd
service postgresql start  
service unicorn start  
nginx # Don't use service  

exit and commit the container.

Create a file on the host with the name Dockerfile and fill it with the following:

FROM my_app_image

# If unicorn doesn't start uncomment next line 
# and change the sock name to yours.
# RUN rm /tmp/unicorn.sock 

RUN chmod +x /usr/bin/start.sh  
RUN echo "daemon off;" >> /etc/nginx/nginx.conf

EXPOSE 80

# If further deployments are needed uncomment next line
# to expose the ssh port
# EXPOSE 22

ENTRYPOINT /usr/bin/start.sh  

Now make the final Docker image. Run in the folder of Dockerfile:

sudo docker build -t my_app_final_image .  

Run it!

Now the app is safely packed into a container. Run it with:

sudo docker run -d -p 80:80 my_app_final_image  

Or

sudo docker run -d -p 80:80 -p 49167:22 my_app_final_image  

for an extra ssh port.

Move the container

We went through all these trouble to make the moving easier. Here is how to move the whole app to another server:

  1. Use sudo docker ps to find out the container id, then stop it with sudo docker stop <id>;
  2. Commit the container;
  3. Use sudo docker save my_app_final_image > repository.tar to export the image;
  4. Copy the tar file to the new server (which, obviously, must have docker installed);
  5. Use sudo docker load < repository.tar to import the image;
  6. Run the image and we are done.