I am sure you have heard of Docker, but have you ever actually deployed a real app on it? How would you even start to move Heroku's 4+ million apps into Docker Containers?

Not many people have even tried. Building an app on Docker can be incredibly hard and frustrating. Not at all like using Heroku where everything is taken care of for you. With Docker, you have to learn about Dockerfiles and then try to get one that works just right with your code.

If you are lazy (like me) and want to just try out Docker with no fuss, I will guide you through the whole process from start to finish. In the end, we will have generic containers we can use with any Docker Developer Tools like CoreOS or Docker Compose manifest files. We will create:

  • Easy: a simple Node application container
  • Medium: a Rails application container
  • Hard: a HHVM WordPress application container

All without learning anything about Linux Containers or Dockerfiles.

Installing Docker

If you are on a Mac, the install process has been greatly improved. You can now get Docker running in just seconds:

$ 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

How to Turn Any Heroku App Into a Docker Container Service

You can use github.com/CenturyLinkLabs/building (like progrium/buildstep but without tarfiles and with extra goodies) to turn any Heroku-compatible app into a Docker Container:

$ sudo gem install building
$ cd path/to/app
$ building myapp      
    create  Dockerfile    
    building  docker build -t myapp:latest .

Hint #1: To run your app, try: docker run -d -p 8080 -e "PORT=8080" myapp

Hint #2: To re-build your app, try: docker build -t myapp .

$ docker push myapp

(Optional: this will allow you to share your app container with anyone in the world via docker pull myapp. Make sure to make it a private repo if it has your private code in it)

Yes, that's it. Everything else is handled for you. Code discovery: done. Dependencies: done. Startup script: Done. Heroku on Docker: Done.

Containerizing Your Node App

This works with any technology: Ruby, Node, Java, Play, Python, PHP, Clojure, Go, Dart and more. Don't believe me? Let's try a simple Node application.

$ cat server.js
var PORT = process.env.PORT || 8080;
var express = require("express");

var app = express();
app.use(express.static(__dirname + "/public"));

app.get("/", function(req, res){
    res.send("Hello, World!");     


$ cat package.json
    "name": "dokku-demo-application",
    "version": "1.0.0",
    "private": true,
    "engines": {
        "node": ">=0.10.0",
        "npm": ">=1.3"
    "dependencies": {
        "express": "~3.0"

$ building myuser/container-name
Uploading context 5.632 kB
Uploading context
Step 0 : FROM ctlc/buildstep:ubuntu13.10
-> a5432f93c775
Step 1 : ADD . /app
-> fa81359c2c88
Step 2 : RUN /build/builder
-> Running in 80ee61eee492
Node.js app detected

PRO TIP: Avoid using semver ranges starting with '>' in engines.node (See https://devcenter.heroku.com/articles/nodejs-support)

--> Requested node range: >=0.10.0
--> Resolved node version: 0.10.26
--> Downloading and installing node
--> Writing a custom .npmrc to circumvent npm bugs
--> Installing dependencies
npm http GET https://registry.npmjs.org/express
npm http 200 https://registry.npmjs.org/express
--> Caching node_modules directory for future builds
--> Cleaning up node-gyp and npm artifacts
--> No Procfile found; Adding npm start to new Procfile
--> Building runtime environment
--> Discovering process types Procfile declares types -> web
--> 163e52f5f836
Step 3 : CMD /start web
--> Running in 3ce46a2994d7
--> 2b6ce52c1812
Successfully built 2b6ce52c1812
Removing intermediate container 09487f499f72
Removing intermediate container 80ee61eee492
Removing intermediate container 3ce46a2994d7

To run your app, try something like this:

docker run -d -p 8080 -e "PORT=8080" myuser/container-name:latest

In my case:

$ docker run -d -p 8080 -e "PORT=8080" myuser/container-name:latest 000a16dc433be0f0d32b2fa80d7fc842a6cb05369776cdc44e2906da3d465468
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 000a16dc433b myuser/container-name:latest /bin/sh -c /start we 12 seconds ago Up 11 seconds>8080/tcp focused_mccarthy
$ boot2docker down
$ VBoxManage modifyvm "boot2docker-vm" --natpf1 "tcp-port49193,tcp,,49193,,49193";
$ boot2docker up
$ curl Hello, World!

Containerizing Your Rails App

Ok, that was cool, but how about a Rails app? Can you do a Rails app?

$ rails new myrailsapp
$ cd myrailsapp  
$ building myuser/rails-container-name
Uploading context 85.5 kB
Uploading context
Step 0 : FROM ctlc/buildstep:ubuntu13.10
-> a5432f93c775
Step 1 : ADD . /app
-> 770f66d24f09
Step 2 : RUN /build/builder
-> Running in 0cd713d0d6e8
Ruby app detected
--> Compiling Ruby/Rails
--> Using Ruby version: ruby-2.0.0
--> Installing dependencies using 1.5.2
--> Writing config/database.yml to read from DATABASE_URL
--> Preparing app for Rails asset pipeline
--> Discovering process types
Default process types for Ruby -> rake, console, web, worker
-> a78e12a093ed
Step 3 : CMD /start web
-> Running in ddb1d8b13f8d
-> 9e2ea01bdbbf
Successfully built 9e2ea01bdbbf
Removing intermediate container 4089b1d484f2
Removing intermediate container 0cd713d0d6e8
Removing intermediate container ddb1d8b13f8d

To run your app, try something like this: docker run -d -p 8080 -e "PORT=8080" myuser/rails-container-name:latest

$ docker run -d -p 8080 -e "PORT=8080" myuser/rails-container-name:latest c4a05c8bfbb67f43ea53d661d14bd0e018a54a4c9389572105345f82326cf39f

Containerizing Your WordPress Site and Running HHVM

Ok, that was cool, but how about WordPress running HHVM?

$ curl http://wordpress.org/latest.tar.gz | tar xvz
$ cd wordpress
$ building myuser/wordpress-container-name
Uploading context 17.67 MB
Uploading context
Step 0 : FROM ctlc/buildstep:ubuntu13.10
-> a5432f93c775
Step 1 : ADD . /app
-> 12f02e9a07ca
Step 2 : RUN /build/builder
-> Running in 592e7f8d4778
PHP (classic) app detected
--> Bundling NGINX 1.4.4
--> Bundling PHP 5.5.10
--> Bundling extensions
--> Setting up default configuration
--> Vendoring binaries into slug
--> Discovering process types
Default process types for PHP (classic) -> web
-> 935abb4b0b50
Step 3 : CMD /start web
-> Running in 90aa3b71513a
-> 2d85ad2102c8
Successfully built 2d85ad2102c8
Removing intermediate container ffff9fab4bf2
Removing intermediate container 592e7f8d4778
Removing intermediate container 90aa3b71513a

To run your app, try something like this: docker run -d -p 8080 -e "PORT=8080" myuser/wordpress-container-name:latest

$  docker run -d -p 8080 -e "PORT=8080" myuser/wordpress-container-name:latest 21fe085ce024e58d248fb64c1c8b9f914f3c09acc039d734897ea0bf2f36f3b8

Wait, that's not HHVM. That's nginx. No fair, I cheated. Now to do it right:

$ building -b https://github.com/hhvm/heroku-buildpack-hhvm.git -f ctlc/buildstep:ubuntu12.04
myuser/wordpress-container-name hhvm
Uploading context 17.67 MB
Uploading context
Step 0 : FROM ctlc/buildstep:ubuntu12.04
-> aa021eb57a6f
Step 1 : RUN git clone --depth 1 https://github.com/hhvm/heroku-buildpack-hhvm.git /build/buildpacks/heroku-buildpack-hhvm
-> Using cache
-> 16cf1eb81d80
Step 2 : RUN echo https://github.com/hhvm/heroku-buildpack-hhvm.git >> /build/buildpacks.txt
-> Using cache
-> f6f51ceb615f
Step 3 : ADD . /app
-> ff188b8dfe49
Step 4 : RUN /build/builder
-> Running in 7ebccf86608d
PHP (HHVM) app detected
--> Downloading HHVM from http://dl.hhvm.com/heroku/hhvm-nightly_2014.03.20~lucid.tgz     --> Sourcing config.hdf
--> Custom config.hdf not found, applying default
--> Installing dependencies using Composer
--> composer.json not found
--> Discovering process types
Default process types for PHP (HHVM) -> web
-> 94286be3d51d
Step 5 : CMD /start web
-> Running in 01481e9e815e
-> c272d7ed4fc0
Successfully built c272d7ed4fc0
Removing intermediate container d2bd8e443091
Removing intermediate container 7ebccf86608d
Removing intermediate container 01481e9e815e

To run your app, try something like this: docker run -d -p 8080 -e "PORT=8080" myuser/wordpress-container-name:hhvm

$  docker run -d -p 8080 -e "PORT=8080" myuser/wordpress-container-name:hhvm ee674c7428752c8e4a20e21fc8a63485899be083287a9db47b6b9e16b97ae4ff

"But With Heroku, I Can Scale using the Command Line!"

With building you can too. There is a -o flag that lets you set a fig.yml output file which you can pass to Fig. (Edit: Fig is now Docker Compose)

$ brew install python # if you are on a Mac
$ sudo pip install -U fig
$ building -o fig.yml myuser/wordpress-container-name:hhvm
$ fig up -d
Creating myapp_web_1...
$ fig scale web=3 Starting myapp_web_2...
Starting myapp_web_3...
$ fig ps
Name Command State Ports
myapp_web_3 /start web Up 49192->8080/tcp
myapp_web_2 /start web Up 49191->8080/tcp
myapp_web_1 /start web Up 49190->8080/tcp

Notice that when you combine building with fig, you never need to manually run a docker command again. Ahhh, abstraction.


One of the most powerful parts of Docker is the fact that you can modify every byte of the underlying "slug" as Heroku calls it. You can put any binaries in any locations. You can build your own Docker images from scratch. But that power comes at a price: ease of use. Tools like building and fig set out to bridge the gap and let everyday developers take advantage of cool new technology without having to get a PhD in DevOps.