Developing Django Project with Docker: The Dockerfile

Docker is a headache to learn but once you get the hang of it and understand all of its concepts and moving pieces, then it works extremely well. I say 'all' loosely here of course, since you can get 95% of what you will ever need by learning maybe 10% of its featureset.

I can see that using Docker would give even more benefits when developing a project with multiple languages and frameworks such as having a React front-end with your Django application. This would mean that the packaging requirements usually managed with the package.json file could also be managed with Docker.

During my time learning about Docker and the development setup and especially making it play nicely with pipenv, I learned that the configuration is very picky. I spent countless hours researching, configuring, building, testing and above all, learning!

Anyway, let me guide you through the Dockerfile that I am using as at the time of writing this guide.

Dockerfile: In Full

# Official Python runtime as the base image
FROM python:3.7.3-alpine

RUN apk --update add \
    build-base \
    postgresql \
    postgresql-dev \
    libpq \
    gettext \
    nginx \
    # Pillow dependencies
    jpeg-dev \
    zlib-dev

# Set working directory
WORKDIR /code

# Add metadata to the image
LABEL author="Wayne Lambert <[email protected]>" \
    version="2019.07" \
    description="Docker image for portfolio site. Hosted at https://waynelambert.dev"

# Install pipenv from PyPI
RUN pip install pipenv

# Copy local files to container
COPY Pipfile Pipfile.lock /code/

# Install project dependencies using exact versions in Pipfile.lock
RUN pipenv install --system --ignore-pipfile --deploy --dev

# Copy the contents of code folder locally to the code directory in container
COPY . .

# Run script file.
CMD ./run-dev.sh

Let's walk through what is declared within the Dockerfile above step-by-step...

# Official Python runtime as the base image
FROM python:3.7.3-alpine

This line pulls down a streamlined version of Python specifically suitable for the Alpine Linux distribution. This acts as the basis for the remainder of the docker build process.

RUN apk --update add \
    build-base \
    postgresql \
    postgresql-dev \
    libpq \
    gettext \
    nginx \
    # Pillow dependencies
    jpeg-dev \
    zlib-dev

This step adds or updates some dependency packages that are required for my project. apk is Alpine's package manager command in a similar way to Ubuntu's apt command.

  • Postgresql is for the project's database
  • nginx is for the web server that would be used in development. It is not really required for the development build.
  • The Pillow dependencies are for image management within the project.

 

# Set working directory
WORKDIR /code

This sets the work directory to a folder location within the container being created called /code. I have found that it is useful to have this a slightly different than you local development environment. In my local development environment, I tend to call the source folder of the project src. It should also be noted that if the directory does not exist within the container then setting the WORKDIR in the build makes the directory.

RUN mkdir code

There is no need to place a line like this before it.

# Add metadata to the image
LABEL author="Wayne Lambert <[email protected]>" \
    version="2019.07" \
    description="Docker image for portfolio site. Hosted at https://waynelambert.dev"

This just adds some metadata to the image that you're creating which might be useful when you put the image onto Docker Hub later.

# Install pipenv from PyPI
RUN pip install pipenv

Prior to this line being executed with the build script, pipenv does not exist within the container's installation of Python which was pulled as the base image from Docker Hub at the start of the build.

# Copy local files to container
COPY Pipfile Pipfile.lock /code/

The Pipfile and Pipfile.lock only exist locally within your project's root directory. These files need to exist within the container's working directory, so that they can be used by the container's recently downloaded pipenv package to install the remainder of your project's dependencies.

# Install project dependencies using exact versions in Pipfile.lock
RUN pipenv install --system --ignore-pipfile --deploy --dev

This command uses pipenv to run a system installation using Python's default pip package manager to install of the project's dependencies including those that are labelled as 'dev-packages' within the Pipfile. If you omit the --dev flag, then the command will only install those required for production.

The --ignore-pipfile flag means that the packages are installed as per the strict version numbers specified in the 'Pipfile.lock'

The --deploy flag ensures that your Pipfile.lock file is up to date.

# Copy the contents of code folder locally to the code directory in container
COPY . .

This copies all the folders and files within your local development environments source folder (in this example, src folder) into your working directory folder within your Docker container (in this example, the folder we called /code.

# Run script file.
CMD ./run-dev.sh

... and finally, this file runs a shell script that is now accessible from within the container's working directory. The contents of the shell script is below.

#!/bin/bash

python manage.py migrate

python manage.py runserver 0.0.0.0:8001

This script is being run from within the container and therefore does not need to be preceeded with docker exec...

The first command migrates the history of your migration files from within your project. You will need to make sure that these migrations are all sound.

The second command spins up a webserver that is accessible from your browser by going to either 127.0.0.1/8001 or localhost:8001

Since the docker-compose.yml file invokes the build process listed within the Dockerfile, there is a mapping that maps the ports on the Django project's service (often called 'web') from port 8000 to 8001. Django's development port is port 8000 and I have chosen to map it to port 8001 within the container. I have found this another useful indicator that you are browsing your project through a Docker container rather than Django's standard development server.

The Dockerfile on GitHub for this particular post is here. The link below is where the project's most-up-date version is in case I find further enhancements to the build process.