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:
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.
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.
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.
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.
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.
Containers are immutable, meaning that when you change the configuration of an image, you have to rebuild the image and run a new instance.
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
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
.
To start, run the below command and follow the steps afterwards:
django-admin startproject dj_docker_drf
sample
rest_framework
and sample
to the INSTALLED_APPS
list in settings.py
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'})
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 = [‘*’]
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.
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 containerENV 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 containerRUN mkdir -p $DockerHOME
: This creates the directory with the specified path assigned to the DockerHOME
variable within the imageWORKDIR $DockerHOME
: This explicitly tells Docker to set the provided directory as the location where the application will reside within the containerRUN pip install --upgrade pip
: This updates the pip
version that will be used to install the dependencies for the applicationCOPY . $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 containerRUN pip install -r requirements.txt
: This command installs all the dependencies defined in the requirements.txt
file into your application within the containerEXPOSE 8000
: This command releases port 8000 within the container, where the Django app will runCMD python manage.py runserver
: This command starts the server and runs the applicationTo run the app, you need to perform two steps:
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.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.
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:
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).
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:
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.
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.
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
fileThe 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
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
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 } }
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!
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowBuild scalable admin dashboards with Filament and Laravel using Form Builder, Notifications, and Actions for clean, interactive panels.
Break down the parts of a URL and explore APIs for working with them in JavaScript, parsing them, building query strings, checking their validity, etc.
In this guide, explore lazy loading and error loading as two techniques for fetching data in React apps.
Deno is a popular JavaScript runtime, and it recently launched version 2.0 with several new features, bug fixes, and improvements […]
6 Replies to "Dockerizing a Django app"
Nobody cares about Django, can you make the make same article on Dockerizing a React or Svelte App?
how does it work, that the browser is able to resolve this url: https://0.0.0.0:8000 ?
Nobody cares about React or Svelte (wtf is even that!) – Kimaru, thanks for making an article on Dockerizing a Django App.
Thanks for writing this article.
Why are you running
CMD python manage.py runserver
instead of using something like gunicorn?
The app is running in the container but curl localhost:8000 gives an empty reply