Docker is the new kid on the block. I believe it is going to change how we develop software. Over the past few months, I have been trying to fit Docker into my daily workflow. I finally cracked the formula. It is amazing how easy it is. We are going to piece together make, docker, and docker-compose (formerly known as fig) to streamline the workflow.
For the purpose of this article, we are going to assume we have a web application which needs a postgres database as the backend. With the power of docker and docker compose, we can quickly spin up the database quickly.
Let’s just say you already have the following Production Dockerfile configured.
FROM kennethzfeng/dockerize-python:2.7.8-onbuild EXPOSE 8000 ENV APPLICATION_ENV Production CMD ["gunicorn", "app:app", "--worker-class", "gevent", "-b", "0.0.0.0:8000"]
You need to create a development version of Dockerfile called it something like
Dockerfile.dev. Since we are going to mount the repository on the host machine to the web container instead of copying them over to the container on every signle build, this not only will save us tons Of time when building images, but also will serve as our hot reload.
Specifically, we mount the repository directory
. on the host machine to
/usr/src/app inside the web container.
FROM kennethzfeng/dockerize-python:2.7.8 RUN mkdir -p /usr/src/deps WORKDIR /usr/src/deps COPY requirements.txt /usr/src/deps/ RUN pip install -r requirements.txt EXPOSE 8000 ENV APPLICATION_ENV Development VOLUME /usr/src/app WORKDIR /usr/src/app CMD ["python", "run_dev.py"]
Building the Image
At the time of writing this, Docker Compose doesn’t support building image using any file other than the default
Dockerfile. (If you find a way to do this, please let me know.) A workaround was using a Makefile goal to automate this.
docker_build_image: docker build -f Dockerfile.dev --name demo-app-dev .
Docker-Compose is very important to this whole workflow because it saves us from managing the container lifecycle ourselves.
# create the container docker run --name abc demo-app-dev # done with the container # kill the container and remove the container docker kill abc docker rm abc
This is just for managing the web container. We are not even couting all of the other work you need to do with the database container and linking them together.
web: image: demo-app-dev volumes: - .:/usr/src/app ports: - "80:8000" links: - db db: image: postgres:9.3 ports: - "5432:5432" environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: "" PGPASSWORD: ""
With docker-compose, you can build up a quite a bit of automation with make which you can then treat the entire stack as a service that you can just do service up and service down.
app=demo-app docker_dev_build: docker build -t $(app)-dev -f Dockerfile.dev . docker_dev_up: docker-compose -f dev.yml up docker_dev_rm: docker-compose -f dev.yml rm
For initializing the database for the first time, we can set up have docker-compose execute the psql utility to create some databases for development and testing. In addition, I added the psql goal to give me quick access to the psql utility inside the container.
create_db: docker-compose -f dev.yml run db sh -c \ 'psql -h "$$DB_PORT_5432_TCP_ADDR" -p "$$DB_PORT_5432_TCP_PORT" -U "$$DB_ENV_POSTGRES_USER" -c "CREATE DATABASE core"' docker-compose -f dev.yml run db sh -c \ 'psql -h "$$DB_PORT_5432_TCP_ADDR" -p "$$DB_PORT_5432_TCP_PORT" -U "$$DB_ENV_POSTGRES_USER" -c "CREATE DATABASE test"' psql: docker-compose -f dev.yml run db sh -c \ 'psql -h "$$DB_PORT_5432_TCP_ADDR" -p "$$DB_PORT_5432_TCP_PORT" -U "$$DB_ENV_POSTGRES_USER"'
Once we have all of the databases created, we can execute the init script from Python which will create all the tables. Also, I can run my unit test within the same environment.
init_db: docker-compose -f dev.yml run web python manage.py init test: docker-compose -f dev.yml run web nosetests -v
With this approach, we can produce a reproducible environment for development and testing. Instead of using a different database such as SQLite, or setting up a local instance of Postgres which will like to change over time, we can reproduce the same environment rapidly using make, docker, and docker-compose.