Introduction

I have been working with Django for the last two years here at Accendero. We are working on a project that will be deployed in a Docker container, so I started looking into what it takes to create one for an existing Django project. The Docker documentation explains how to build an image and create a Django project inside of it. Using this as a resource, I figured out how to build a Docker image for an existing Django project. This article shows the steps to accomplish this. The final project, including all the pieces needed for the Docker image, is available in our company Bitbucket.

Dockerfile

We start with an existing Django project. The source code is available at the existing-django-project tag of the git repository for this article. Clone the repository from Bitbucket and check out this tag. Then create a new file named Dockerfile in the root directory of the project and add the following contents:

FROM python:3
ENV PYTHONUNBUFFERED 1
RUN mkdir /src
WORKDIR /src
COPY requirements.txt /src/
RUN pip install -r requirements.txt
COPY . /src/
CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]

Analysis

Let’s look at each line to understand what is happening here

FROM python:3

Start by using the base Python 3 image. At the time of this writing, python:3 is an alias for python:3.8.0.

ENV PYTHONUNBUFFERED 1

Set the PYTHONUNBUFFERED environment variable so that no output is buffered. This ensures that we see complete error messages in case anything goes wrong.

RUN mkdir /src

Create a directory named src in the Docker image. This is where we will put our source code.

WORKDIR /src

Set the current working directory to /src. Later commands in the Dockerfile can assume paths start at this directory.

COPY requirements.txt /src/

Copy requirements.txt from the host directory to /src/ in the image.

RUN pip install -r requirements.txt

Install all of the Python package dependencies.

COPY . /src/

Copy the entire current working directory on the host to the /src/ directory. It is tempting to do this before running pip install rather than just copying requirements.txt by itself. The reason for doing it this way is that when we later run docker image build, it will cache the results of each COPY command. If the contents being copied haven’t changed compared to that cache, then docker will skip the COPY. On the other hand, if there are any local changes, then docker invalidates the cached COPY and all of the following commands. In this case, only changes to requirements.txt will require running pip install again. Changes to the rest of the code won’t.

CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]

Define the command to run when the container starts. In this case, we will run the Django development server on port 8000.

Build the Docker Image

Now run the following command to build the docker image:

docker image build -t django-docker .

The -t option provides a tag which we can use to refer to this image in other commands.

Run a Docker Container

Type the following command to run a container from the built image:

docker container run --publish 8000:8000 --name django-docker django-docker

The --publish option tells docker to forward requests to port 8000 on the host machine to port 8000 of the container.

Then we give the container a name with --name django-docker. Finally we specify the image to use by referring to the tag that we gave with the -t option to docker image build.

Result

With the container running, open your favorite browser and go to 127.0.0.1:8000/rest/. You should see the default Django Rest Framework interface for your app.

REST API in a browser

Conclusion

Now we have a Docker image that contains a complete copy of a Django project. This is great for deploying your app, but if you change the code, you will have to rebuild the image to reflect those changes. This isn’t ideal for development when you want to change a single line and see how it affects your app. Additionally, the container is not production-ready because it runs the Django development server with ./manage.py runserver. Future articles will address both of these issues.