Docerizing Django CMS

In this tutorial I’ll be showing one way of moving Django CMS project to Docker Containers. As with anything Docker this can be accomplished in many different ways. For development I’m using Mac and project at the end will be hosted on DigitalOcean.

To accomplish our task we’ll be relying on Docker Compose and Docker Machine

We’ll be using Nginx as our front end web server and PostgreSQL as our database.

Repo with code for this article can be found here: https://github.com/kooba/django-cms-docker

Prerequisites

Few assumptions are that you have a Python environment with Virtualenv configured and you have some degree of familiarity with it.

Let’s check we have the right versions of all client tools installed:

$ virtualenv --version
13.0.3
$ docker -v
Docker version 1.6.2, build 7c8fca2
$ docker-compose --version
docker-compose 1.2.0
$ docker-machine --version
docker-machine version 0.2.0 (8b9eaf2)

Local Django CMS Setup

First we want to setup entire project locally by using djangocms-installer which will streamline process of pre-configuration of majority of Django CMS project aspects.

Open terminal and cd to empty directory where project will be located.

Create new python environment:

$ virtualenv env
New python executable in env/bin/python
Installing setuptools, pip, wheel…done.

Activate it:

$ source env/bin/activate

If everything went well your prompt should be prefixed with (env)

(env)my-mac:django-cms-docker jakub$

Install djangocms-installer

$ pip install djangocms-installer

Create a new directory called web and cd into it:

$ mkdir web
$ cd web

Run djangocms-installer to create your site

djangocms -p . mycms

You’ll be asked several questions about the initial configuration for your site. Here are suggested answers:

  1. Database configuration (in URL format) [default sqlite://localhost/project.db]: hit return to keep default database connection to SQLite. We’ll switch to PostgreSQL later on in the article.
  2. django CMS version (choices: 2.4, 3.0, 3.1, stable, develop) [default stable]: hit return to keep default
  3. Django version (choices: 1.4, 1.5, 1.6, 1.7, stable) [default stable]: hit return to keep default
  4. Activate Django I18N / L10N setting (choices: yes, no) [default yes]: hit return to keep default
  5. Install and configure reversion support (choices: yes, no) [default yes]: hit return to keep default
  6. Languages to enable…: en
  7. Optional default time zone [default Europe/London]: hit return to keep default
  8. Activate Django timezone support (choices: yes, no) [default yes]: hit return to keep default
  9. Activate CMS permission management (choices: yes, no) [default yes]: hit return to keep default
  10. Use Twitter Bootstrap Theme (choices: yes, no) [default no]: yes
  11. Use custom template set [default no]: no
  12. Load a starting page with examples after installation: no

You’ll be asked to create Admin user. We will have to repeat this step once we move to PostgreSQL

When moving project into docker container we’ll be installing django and djang-cms dependencies via pip so it’s a good time to gather all of these dependencies in requirements.txt:

$ pip freeze > requirements.txt

We’ll be using gunicorn and postgres to run our app. Open requirements.txt and apppend this to the end:

gunicorn==19.3.0
psycopg2==2.6

Adding Nginx

Move one directory up, create django_project file inside nginx/sites-enabled folder:

$ cd ..
$ mkdir nginx
$ cd nginx
$ mkdir sites-enabled
$ cd sites-enabled
$ touch django_project

Enter this nginx configuration in django_project file:

Adding Docker files

For our web project and Nginx setup we’ll need custom Dockerfiles.
Create Dockerfile in root of Nginx directory:

$ cd ..
$ touch Dockerfile

Enter this docker configuration to Dockerfile:

Move to web directory and create Docker file there:

$ cd ../web
$ touch Dockerfile

Enter this docker configuration to Dockerfile:

Docker-Compose Development config

Create docker-compose.yml in root of our project:

$ cd ..
$ touch docker-compose.yml

Enter this configuration:

With this configuration we are mounting our local web folder to a container’s /usr/src/app, this will allow for any code changes to be immediately visible upon save.

See the documentation to learn more about docker-compose yml configuration options.

Environment Variables

At this point our Django CMS database connection string is hardcoded inside of web/mycms/settings.py file. We want to move it out to .env file so that we don’t commit that information to our git repo and also can provide different setting per environment (dev, test, production)

Create .env file in the root of our project

$ touch .env

Docker Compose env_file expects VAR=VAL format for all your variables.

In our case we will be moving values for Secret Key and Database connection to our .env file. From web/mycms/settings.py file grab content of SECRET_KEY and DATABASES and populate our new .env file with this information:

Change settings.py to accept these values:

Build your docker images and run the app

First we need to create our development docker-machine. This means we will create new boot2docker VM in our Virtual Box. We will call it mycms-dev:

$ docker-machine create -d virtualbox mycms-dev

You can confirm your machine is up and running with ls command:

$ docker-machine ls

You should see the list of all the machines and their statuses:

(On subsequent runs all you’ll have to do is activate existing machine with command: $ docker-machine active mycms-dev)

Point docker client to your newly create machine:

$ eval "$(docker-machine env mycms-dev)"

Run docker compose build command to build docker images based on docker-compose.yml file (if no file flag is given docker-compose.yml is used by default)

$ docker-compose build

After build is complete we want to run our containers:

$ docker-compose up -d

Last two steps will take a while so grab a coffee or an ice cream.

Create new Postgres user and database

Postgres Docker image comes with default postrges user but we can easily create our own for security reasons.

Let’s ssh to our host machine (while our containers are up and running) and then start bash process in our postgres container:

$ docker-machine ssh mycms-dev
$ docker exec -it djangocmsdocker_postgres_1 bash

Run psql under default postgres user:

$ psql -U postgres

Create new user and database matching our .env file:

$ CREATE USER mycms_user WITH PASSWORD 'Mycm$_Pa$$';
$ ALTER USER mycms_user SUPERUSER;
$ CREATE DATABASE mycms WITH OWNER = mycms_user;

Quit psql and exit container:

$ \q
$ exit

Database Migrations and Static Assets

While still within host machine start bash within your web container:

$ docker exec -it djangocmsdocker_web_1 bash

Collect static assets to be served by Nginx:

$ python manage.py collectstatic --noinput

Run database migrations:

$ python manage.py migrate

Create first Django CMS admin user:

$ python manage.py createsuperuser

Provide username, email and password when asked.

Quit your container and a machine:

$ exit
$ exit

Run It

Grab IP address of your docker-machine by issuing ls command:

$ docker-machine ls

The ip is listed in URL column. Open browser and use it:

Use username/password for Django CMS super user created in a previous step to login to the admin section.

Commit to git

It would be a good time to commit your work to a git repository

$ git init

Add .gitignore to skip unwanted files and folders.

We’re skipping content of env folder, .env file and all python bytecode files.

There is a left over project.db file in web directory which can be removed.

Let’s commit everything else:

$ git add .
$ git commit -m "Django CMS first commit"

Deployment to Production

We’ll be deploying to DigitalOcean. After signing up and logging in to your account you’ll be able to generate Personal Access Token. We’ll be using this token to generate new droplet where all our containers will be hosted.

Run $ docker-machine create with your newly created token and call it mycms-prod

$ docker-machine create \
-d digitalocean \
— digitalocean-access-token=ADD_YOUR_TOKEN_HERE \
mycms-prod

After successfully creating your new droplet, point your docker client to new machine:

$ eval “$(docker-machine env mycms-prod)”

Copy existing docker-compose.yml file and name it docker-compose-prod.yml

Please note our production file will not specify volume for web container since it’s not needed in production additionally you can use different .env file for each of the environments if you desire.

Build and run production image:

$ docker-compose build
$ docker-compose -f docker-compose-prod.yml up -d

Just as we did in above steps for dev environment we will have to create new database user, collect static assets and create Django CMS admin user.

Execute all of the commands one by one and wait for each to complete:

$ docker-machine ssh mycms-prod
$ docker exec -it djangocmsdocker_postgres_1 bash
$ psql -U postgres
$ CREATE USER mycms_user WITH PASSWORD 'Mycm$_Pa$$';
$ ALTER USER mycms_user SUPERUSER;
$ CREATE DATABASE mycms WITH OWNER = mycms_user;
$ \q
$ exit
$ docker exec -it djangocmsdocker_web_1 bash
$ python manage.py collectstatic --noinput
$ python manage.py migrate
$ python manage.py createsuperuser
$ exit
$ exit

Run docker-machine ls to get IP for your new droplet and checkout your new site life!

Summary

We are now able to run isolated environment for our Django-CMS development and easily push it to production. In real scenario you would want to push your locally built docker images to Test environment first, run your tests and only if passed, commit your image and copy it to your production machines. This would get you closer to the goal of immutable infrastructure advocated by docker community.

Thanks to Real Python article for the inspiration.