Quickstart for a Django project with Docker

Posted on Dec 3, 2017

In this post we will see how to set up quickly a Django project with Docker, so that it will be less painful to set up a CI pipeline on any environment of your choice (AWS, DigitalOcean, etc.).

This is something that should be done as soon as possible when bootstrapping your project - stop doing things that require a set of pre-installed packages only available on your machine. The earlier you do this, the less painful it will be - yourself included!

1. Clone and/or Create a Repository

You will need a repository for your project, right? So, either clone one already existing or create a new one.

You can clone the repo I created for this tutorial so that we can refer to the same code:

$ git clone https://github.com/markon/django-rest-quickstart

2. Create environment with Pipenv

Pipenv is the recommended Python packaging tool.

Install it:

$ pip install pipenv

and create a new environment in your repo:

$ pipenv --three # if you want to use python 3

and verify that everything went fine:

Pipfile found at {...}/tutorials/django-rest-quickstart/Pipfile. 
Considering this to be the project home.

If you open the file, you will find the following content:

$ cat Pipfile
[[source]]
url = "https://pypi.python.org/simple"
verify_ssl = true

Essentially, a brand new project! However, we know we want to use Django, so, let’s add some dependencies:

[packages]
psycopg2 = ">=2.7.3.1"
Django = ">=1.11,<2.0"

[requires]
python_version = "3.6"

In this tutorial we don’t care right now about the specific Django version. Whatever is recent enough should be good - however, you should not do that on production! Many things can be updated between two versions, and having such a file checked in implies that whatever recent version will be taken - don’t use version ranges, unless you know what you are doing.

As we want to have deterministic builds, we could use Pipenv lock, that generates a file containing the specific versions we want to use.

$ pipenv lock
Locking [dev-packages] dependencies...
Locking [packages] dependencies...
Updated Pipfile.lock!

This way, we can easily “freeze” the dependencies to a specific version, so that everyone else will install exactly the same dependencies as we do - no more problems like hey, I am using version 1.7.0.1 while you use 1.7.0.4. One version for all of us!

3. Create a Dockerfile

Before starting a Django project, we really want to make sure not to skip this important step - creating a Dockerfile:

FROM python:3.6-alpine

ENV PYTHONUNBUFFERED 1

RUN apk add --repository http://dl-cdn.alpinelinux.org/alpine/v.3.6/main --no-cache postgresql-dev
RUN apk add --repository http://dl-cdn.alpinelinux.org/alpine/v.3.6/main --no-cache gcc
RUN apk add --repository http://dl-cdn.alpinelinux.org/alpine/v.3.6/main --no-cache python3-dev
RUN apk add --repository http://dl-cdn.alpinelinux.org/alpine/v.3.6/main --no-cache musl-dev

RUN mkdir /code
WORKDIR /code
ADD . /code/

RUN pip3 install pipenv
RUN pipenv --three
RUN pipenv install --deploy --system

EXPOSE 8000

CMD ["python", "manage.py", "runserver"]

To summarize what it does:

  • Use python:3.6-alpine
  • Install PostgreSQL (we will need it for our project)
  • Create a new directory /code inside the container and the current directory to it
  • Install Pipenv for Python 3
  • Expose port 8000 for our Django web service
  • Run manage.py runserver by default

4. Create a docker-compose.yaml file

Following the official documentation from Docker, let’s create now a docker-compose file:

version: '3'

services:
 db:
   image: postgres
 web:
   build: .
   command: python3 manage.py runserver 0.0.0.0:8000
   volumes:
    - .:/code
   ports:
     - "8000:8000"
   depends_on:
   - db

We mount the current directory as /code, so that we don’t have to build a new Docker image every time we change the code. However, be careful: if you update your dependencies (e.g., a little change in Pipfile), your Docker image needs to be updated, because they come pre-installed.

5. Create a Django project

Now we are finally able to create a Django project! Go to the root of your project

 docker-compose run web django-admin.py startproject djangorestapi .

You should now see a djangorestapi folder and a manage.py in your project.

6. Start coding your app

Finally, we can add some code to create some features. An example I would like you to refer to is based on the previous post - RBAC with django-rest-framework and django-guardian.

The full code is visible in the repository, as it’s a bit out of scope for this tutorial.

7. Test locally

Normally, you would want to run migrations first.

docker-compose run web python manage.py migrate

then you will be able to run

docker-compose up

and wait until your service is up and running. Then you can play with your app available on http://localhost:8000/api/profiles. Please follow the README file to see how to run the examples.

Note: sometimes you may get a ConnectionRefused error, because PostgreSQL is slower than Django to start up. In this case, you could just re-run the command twice.