Recently, I bought a new Macbook Pro for my development needs. Migrating my development environment from my old Fedora-based box turned out to be a nightmare, mostly because I’d installed stuff willy-nilly with no way to repeatably recreate it. I decided to do it the Right WayTM this time. I’ve worked with Vagrant before, but I decided to check out this Docker thing that the Hacker News crowd is going ga-ga over.

Requirements

My app is fairly standard - a python flask app fronted by an nginx reverse-proxy that also serves static content.

I had the following (reasonable) requirements:

  • Quick local development cycle: the code lives on my Macbook, and any changes to my code should auto restart the containerized app. No manual restarts required.
  • Repeatable build process: I want to be able to recreate my dev environment on other machines and preferably use the same build process to deploy on cloud platforms like AWS, GCE or DigitalOcean.

With all the talk about Docker, I couldn’t find a simple step-by-step tutorial that shows you how to create such a setup, so here it is.

Overview

Docker best practices recommend one process per container, so we will need two containers 1, one each for nginx and your flask app. In practice, you will need a third database container, but for simplification, I have omitted it. Only the nginx container will be accessible to the outside world. The flask application container will only be accessible from the nginx container; in fact, it doesn’t even need network access. This process isolation is a major benefit touted by Docker.

Step-by-step walkthrough

The objective of this tutorial is to quickly get you up and running, so I don’t go into detail about what the various docker commands do. I will assume that you have already set up Docker on OS X as detailed here. I will also assume you have built a flask application before and have the code handy on your Mac. Let’s get started.

1. Create a Dockerfile for your Flask app

The nice thing about Docker images is that they’re extensible - you simply inherit most of the work that someone else has already done for you and add your stuff to it. There is an official debian-based Python image. However, I prefer running CentOS given that I’m familiar with the ecosystem, especially yum. I couldn’t find a CentOS 6 image with Python 2.7.8, so I created one here.

Now, let’s create a Dockerfile for your flask app using the CentOS python image as base. Create a file named Dockerfile in your flask app’s root directory. I assume your app is called flaskapp and app.py runs it.

FROM juggernaut/centos-python:centos6

RUN mkdir -p /opt/services/flaskapp/src
COPY . /opt/services/flaskapp/src
VOLUME ["/opt/services/flaskapp/src"]
WORKDIR /opt/services/flaskapp
RUN virtualenv-2.7 venv && source venv/bin/activate && pip install -r src/requirements.txt
EXPOSE 5000
CMD ["/opt/services/flaskapp/venv/bin/python2.7", "/opt/services/flaskapp/src/app.py"]

All this does is copy your source directory into the container, install virtualenv and exposes the default flask port (more on this later).

2. Build Flask app image

From your flask app’s root directory, run this:

docker build -t flaskapp .

This creates a docker image named flaskapp.

3. Run Flask app container

docker run -d -P --name flaskapp -v $PWD:/opt/services/flaskapp/src flaskapp

This will run a container named flaskapp based on the image we created in Step 2. The -v flag will mount your local source directory inside the container, so your local changes will be reflected automatically.

4. Build nginx image

Given that nginx is a stable piece of software and I probably don’t need to muck around with a running nginx container, I just used the official Debian-based nginx image. We’ll create a server block in the nginx config to proxy through to our flask app. Create a new directory (doesn’t matter where) and create the following Dockerfile:

FROM nginx:1.7.8
COPY flaskapp.conf /etc/nginx/conf.d/flaskapp.conf

Now, create a file called flaskapp.conf in the same dir:

server {
    listen 80;

    location / {
        proxy_pass http://flaskapp:5000;
    }
}

Now, build the nginx image as you did for the flask app:

docker build -t nginx-flask .

5. Run nginx container

docker run --name nginx-flask --link flaskapp:flaskapp -v -d -p 8080:80 nginx-flask

The --link flag links the nginx container to the app container and exposes it as a host named with the link alias i.e flaskapp. The -p flag exposes port 80 of the container on port 8080 of your localhost (or boot2docker VM)

That’s it, we’re done! To access your app, find your boot2docker VM IP using boot2docker ip and navigate to http://$IP:8080 in your favorite browser.

  1. fig seems to be a nice tool for managing multi-container setups. However, if you’re like me, you’ll want to understand Docker from the bottom-up before reaching for higher-level tools.