Kimaru Thagana Seasoned Python/Django developer with vast experience working on web and backend applications. Remote work enthusiast and practitioner. Developer by calling, tech-guide-writer by passion.

Dockerizing a Django app

10 min read 2893

Dockerize Django App

​​Editor’s note: This article was updated on 8 April 2022 to provide more information to some key terms, explain each of the commands in the Dockerfile, and break down the Django app setup into clearer steps.

To actualize a Django project, most of the time you need an off-the-shelf solution in the form of a library or dependency.

This is typically not an issue, and is often documented in the requirements.txt file that will contain the list of packages or dependencies along with their respective version that you need to run your project.

The trouble starts when you attempt to share the entire project with another individual who wishes to run and test it because, unfortunately, the user will have to perform the setup from scratch every time you make significant changes in the libraries and dependencies.

This is where containerization and Docker come in. Docker isolates your application and its dependencies and ensures consistent runtime protocols for your applications regardless of the configurations of the servers hosting your application.

This guide will walk you through setting up a Django project with Docker, including:

What is Docker?

Docker is an open-source tool that manages the development, testing and deployment of containerized applications.

It offers hardware virtualization at the OS level and is suitable for modern architecture. This allows developers to package and ship software and its dependencies in order to distribute it as containers.

It’s an incredibly popular containerization platform that solves the library and dependency issues once and for all.

But its best feature? Regardless of host or underlying infrastructure, your containerized application will always run the same way.

We made a custom demo for .
No really. Click here to check it out.

In simple terms, you can now wrap up all the pieces your software needs in a single unit, called a Docker image, then ship or share this image with anyone. As long as the recipient has Docker installed on their machine, they will be able to run or test your project. Gone are the days of wondering why a project works on one machine and not another.

Docker also offers a service called DockerHub that allows you to share and manage Docker images among other developers and larger communities — essentially, it’s a “GitHub” for Docker images.

It shares some similarities with the code repository platform, such as uploading and downloading images via CLI commands contained within the Docker CLI.

Why should you use Docker?

Remote services

A developer can pull Docker images from Docker Hub to any machine that hosts its containers. Implying that you can always retrieve a Docker image, build it, and run an instance of it from wherever you are and whenever you want.

Cost efficiency

Docker allows you to support several containers with one infrastructure rather than using multiple virtual machines to run identical copies of the virtual OS managed by the hypervisor, which can be expensive to maintain.

Scalability

Docker containers can efficiently scale-up applications to withstand more load and automatically decrease the computing load of your application when the amount of requests reduces.

Security

Containers are immutable, meaning that when you change the configuration of an image, you have to rebuild the image and run a new instance.

Prerequisites for using Docker

  • Proficiency in Django development
  • Intermediate level with CLI and bash

Docker installation

This tutorial uses YAML files to perform Docker scripting and executes the files via the Docker CLI. This guide will explore setting up Docker on an Ubuntu machine. If you are using a different OS, you can check out the documentation for getting started with Windows and macOS.

To download and set up Docker, run the command below on your terminal:

sudo apt-get update  
sudo apt-get install docker-ce docker-ce-cli containerd.io  

Setting up and Dockerizing a Django app

This guide assumes you are already proficient in Django, so there won’t be any emphasis on the structure of a Django app. If you are new to Django, here is a tutorial to get you up to speed.

Let’s skip ahead to the steps for running a basic Django REST framework app in Docker and displaying the default page. Consider it the Hello, world! of Django and Docker.

Using the guide provided in this tutorial, you can Dockerize any previous or future Django project you may have, especially one that has libraries listed in requirements.txt.

Step 1

To start, run the below command and follow the steps afterwards:

django-admin startproject dj_docker_drf
  • Navigate into your project folder
  • Start an app named sample
  • Add rest_framework and sample to the INSTALLED_APPS list in settings.py

Step 2

In the views.py file, add the below code snippet that returns the message, “HELLO WORLD FROM DJANGO AND DOCKER”.

from rest_framework.views import APIView  
from django.http import JsonResponse  

class HomeView(APIView):  

 def get(self, request, format=None):
    return JsonResponse({"message":
    'HELLO WORLD FROM DJANGO AND DOCKER'})  

Step 3

Connect the main URL file and the app URL file so that HomeView is the default view when a user accesses the app on the browser.

In order to allow access to the Django app from any server or IP address, ensure that ALLOWED_HOSTS in the settings.py file is set to *, as shown in the snippet below:

ALLOWED_HOSTS = [‘*’]

Step 4

Finally, create a requirements.txt file in your root project folder and add the DRF library:

django-rest-framework==0.1.0  

The app is now ready to be Dockerized.

Creating the Dockerfiles and Docker CLI

Notice that the Dockerfile is named. This is to allow the Docker CLI to track it.

In your project root, create a file named Dockerfile without an extension and add the following snippet to the file:

# base image  
FROM python:3.8   
# setup environment variable  
ENV DockerHOME=/home/app/webapp  

# set work directory  
RUN mkdir -p $DockerHOME  

# where your code lives  
WORKDIR $DockerHOME  

# set environment variables  
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1  

# install dependencies  
RUN pip install --upgrade pip  

# copy whole project to your docker home directory. 
COPY . $DockerHOME  
# run this command to install all dependencies  
RUN pip install -r requirements.txt  
# port where the Django app runs  
EXPOSE 8000  
# start server  
CMD python manage.py runserver  

Let’s explore the Dockerfile and what each of the commands do.

  • FROM python:3.8 : This installs a Python image into the Docker image. This is also the version of Python that will run the application in the container
  • ENV DockerHOME=/home/app/webapp: Here we declare the working directory and assign it to the variable name DockerHOME. This will be the root directory of the Django app in the container
  • RUN mkdir -p $DockerHOME: This creates the directory with the specified path assigned to the DockerHOME variable within the image
  • WORKDIR $DockerHOME: This explicitly tells Docker to set the provided directory as the location where the application will reside within the container
  • RUN pip install --upgrade pip: This updates the pip version that will be used to install the dependencies for the application
  • COPY . $DockerHOME: This copies every other necessary file and its respective contents into the app folder that is the root directory of the application within the container
  • RUN pip install -r requirements.txt: This command installs all the dependencies defined in the requirements.txt file into your application within the container
  • EXPOSE 8000: This command releases port 8000 within the container, where the Django app will run
  • CMD python manage.py runserver: This command starts the server and runs the application

Running the app in Docker

To run the app, you need to perform two steps:

  1. Build the image: This is done using the build command, which uses the Dockerfile you just created. To build the image, run the command below:docker build . -t docker-django-v0.0.This command should be executed in the directory where the Docker file lives. The -t flag tags the image so that it can be referenced when you want to run the container.
  2. Run the image: This is done using the docker run command. This will convert the built image into a running container. To run the app, execute the below command:docker run docker-django-v0.0

You can proceed to view your app in the browser at localhost:8000.

Running multiple containers with Docker Compose

There are instances where you may want to run multiple containers in Docker and execute them in a specific order.

This is where Docker Compose comes in handy.

Docker Compose is a tool for defining and running multi-container applications of any kind. If you have an application comprising several containers, you will use the Docker Compose CLI to run them all in the required order that you specify.

Take, for example, a web application with the following components:

  1. Web server container such as Nginx
  2. Application container that hosts the Django app
  3. Database container that hosts the production database, such as PostgreSQL
  4. A message container that hosts the message broker, such as RabbitMQ

To run such a system, you will declare the directives in a Docker Compose YAML file. Here, you define how the images will be built, on which port will each of the images will be accessible, and most importantly, the order in which the containers should execute (i.e., which container depends on another container for the project to run successfully).

Using Docker Compose with a Django app

Let’s explore Docker Compose using the scenario demonstrated above: a Django app with a PostgreSQL database, RabbitMQ message broker, and an Nginx load balancer. Follow this guide to install the CLI tool on your host operating system.

With Docker Compose (and, similarly to Docker), a particular file with a specific name is required. The CLI tool reads this file and uses it to spin up the Docker images and run them.

To create a Docker Compose file, create a YAML file and name it docker-compose.yml. This ideally should exist at the root directory of your project.

version: '3.7'

services: # the different images that will be running as containers
  nginx: # service name

# location of the dockerfile that defines the nginx image. The dockerfile will be used to spin up an image during the build stage.
    build: ./nginx 

# map the machine port 1339 to the container port 80. Any traffic from 1339 externally will be passed to port 80 of the NGINX container. You can access this container viea localhost:1339
    ports: - 1339:80 


# static storages provisioned since django does not handle static files in production
    volumes:  - static_volume:/home/app/microservice/static 

# will only start if web is up and running
    depends_on: - web 

# restart service when it fails
    restart: "on-failure" 

  web: # service name

#build the image for the web service from the dockerfile in parent directory.  
    build: . 

# command directive passes the parameters to the service and they will be executed by the service. In this example, these are django commands which will be executed in the container where django lives.
    command: sh -c "python manage.py makemigrations &&
                    python manage.py migrate &&
                    gunicorn microservice_sample_app.wsgi:application --bind 0.0.0.0:${APP_PORT}" 

# map data and files from parent directory in host to microservice directory in docker container
    volumes: - .:/microservice 

    - static_volume:/home/app/microservice/static

# file where env variables are stored. Used as best practice so as not to expose secret keys
    env_file: - .env # name of the env file

# name of the image
    image: microservice_app 

# expose the port to other services defined here so that they can access this service via the exposed port. In the case of Django, this is 8000 by default
    expose: 
      - ${APP_PORT} # retrieved from the .env file

    restart: "on-failure"

# cannot start if db service is not up and running
    depends_on: - db


  db: # service name

# image name of the postgres database. during build, this will be pulled from dockerhub and a container spun up from it.
    image: postgres:11-alpine 

    volumes:
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql
      - postgres_data:/var/lib/postgresql/data/

# access credentials from the .env file
    environment: 
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
      - POSTGRES_DB=${DB_NAME}
      - PGPORT=${DB_PORT}
      - POSTGRES_USER=${POSTGRES_USER}

    restart: "on-failure"


  rabbitmq:
    image: 
      rabbitmq:3-management-alpine #image to be pulled from dockerhub during building
    container_name: rabbitmq # container name
    volumes: # assign static storage for rabbitmq to run
      rabbitmq: - ./.docker/rabbitmq/etc/:/etc/rabbitmq/
        - ./.docker/rabbitmq/data/:/var/lib/rabbitmq/
    rabbitmq_logs:  - ./.docker/rabbitmq/logs/:/var/log/rabbitmq/
    environment: # environment variables from the referenced .env file
      RABBITMQ_ERLANG_COOKIE: ${RABBITMQ_ERLANG_COOKIE}
         # auth cretendials
      RABBITMQ_DEFAULT_USER: ${RABBITMQ_DEFAULT_USER} 
      RABBITMQ_DEFAULT_PASS: ${RABBITMQ_DEFAULT_PASS}
    ports: # map external ports to this specific container's internal ports
      - 5672:5672
      - 15672:15672

# can only start if web service is running
    depends_on: 
      - web


volumes:
  postgres_data:
  static_volume:
  rabbitmq:
  rabbitmq_logs:

One of the highlights of Docker Compose is the depends_on directive. From the above script, we can deduce that:

  • Nginx depends on web
  • Web depends on DB
  • RabbitMQ depends on web

With this setup, DB is the first service we need to start up, followed by web, then RabbitMQ, and lastly, Nginx.

When you decide to terminate the environment and stop the running containers, the order will be in reverse — that is, Nginx will be the first to run and DB the last.

Building and running Docker Compose scripts

Just like a Docker script, the Docker Compose script has a similar structure in that it has build and run commands.

The build command will build all the images defined under services within the docker-compose.yml in the order of the dependency hierarchy.

Luckily, there is also a command that combines both build and run — this is called up. To run this command, execute the command here:

 docker-compose up

You can also add the --build flag. This is useful when you’ve run this command before and want to build new images.

docker-compose up --build

Once you’re done with the containers, you may wish to shut them all down and remove any static storage they were using, such as the PostgreSQL static volume. To do this, run the following command:

docker-compose down -V

The -V flag stands for volumes. This ensures that the containers and attached volumes are shut down.

Follow the official documentation to learn more about various Docker Compose commands and their usage.

Supporting files in a Django application

There are some files referenced in the script above that make the file less bulky, thus making code management easier. These include the .env file, the Nginx’s Dockerfile and config files. Below are samples of what each entails:

.env file

The main purpose of this file is to store variables, such as keys and credentials. Environment variables are a set of key-value pairs for the current user environment. This is a safe coding practice that ensures your personal keys are not exposed.

#Django
SECRET_KEY="my_secret_key"
DEBUG=1
ALLOWED_HOSTS=localhost 127.0.0.1 0.0.0.0 [::1] *


# database access credentials
ENGINE=django.db.backends.postgresql
DB_NAME=testdb
POSTGRES_USER=testuser
POSTGRES_PASSWORD=testpassword
DB_HOST=db
DB_PORT=5432
APP_PORT=8000
#superuser details
DJANGO_SU_NAME=test
[email protected]
DJANGO_SU_PASSWORD=mypass123
#rabbitmq
RABBITMQ_ERLANG_COOKIE: test_cookie
RABBITMQ_DEFAULT_USER: default_user
RABBITMQ_DEFAULT_PASS: sample_password

The Nginx Dockerfile

This is hosted in an nginx folder within the root directory. It mainly contains two directives: the image name pulled from Dockerhub; and the location of the configuration files.

Create a folder with the name nginx. In this folder, create another Dockerfile and add the code snippet below:

FROM nginx:1.19.0-alpine

RUN rm /etc/nginx/conf.d/default.conf
COPY nginx.conf /etc/nginx/conf.d

The Nginx config file

This is where you write the Nginx configuration logic. This file should be located in the same folder — the Nginx folder, as the Nginx Dockerfile.

This config file dictates how the Nginx container will behave. Below is a sample script that lives in a file typically named nginx.conf.

upstream microservice { # name of our web image
    server web:8000; # default django port
}

server {

    listen 80; # default external port. Anything coming from port 80 will go through NGINX

    location / {
        proxy_pass http://microservice_app;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $host;
        proxy_redirect off;
    }
    location /static/ {
        alias /home/app/microservice/static/; # where our static files are hosted
    }

}

Conclusion

Docker is a convenient tool for backend developers. The Docker tips and guidelines in this tutorial are vital for DevOps and full-stack developer positions in any organization.

This tutorial demonstrates how you can setup your Docker in your Django application, build a Docker image and run an instance of the image as a container.

We also discussed how to use the docker compose commands, through the Docker-compose.yml file to build and run multiple containers in a specific order.

If you’ll like to learn more about Docker and integrate it in your applications, this documentation is a good resource to explore. You can also explore how to integrate Docker with other tools here.

The Django documentation is a useful resource if you’re also interested in honing or building your skills in Django.

Thanks for reading!

: Full visibility into your web apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page and mobile apps.

.
Kimaru Thagana Seasoned Python/Django developer with vast experience working on web and backend applications. Remote work enthusiast and practitioner. Developer by calling, tech-guide-writer by passion.

4 Replies to “Dockerizing a Django app”

  1. Nobody cares about Django, can you make the make same article on Dockerizing a React or Svelte App?

  2. Nobody cares about React or Svelte (wtf is even that!) – Kimaru, thanks for making an article on Dockerizing a Django App.

Leave a Reply