Docker Django

over 1 year

2023 04

How to Dockerize your Django Project with PostgreSQL

In this tutorial I will provide some examples on how to dockerize your Django application.
We will also setup a PortgreSQL database, running on it's container.

First you need to create your project folder and change directory to the project root.

I recommend you create the following files:
touch .gitignore README.md .env .env.example

Now let's create the requirements.txt for our python backend & add the following content to the file:

Django>=4.0
psycopg2>=2.8

This will make the installation easier. And it's standard for python projects to list your dependencies on the requirements.txt.

Now let's create the Dockerfile which will hold our image recipe to create our Django container.

# syntax=docker/dockerfile:1
FROM python:3
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
WORKDIR /code
COPY requirements.txt /code/
RUN pip install -r requirements.txt
COPY . /code/
This Dockerfile example was taken from 'https://github.com/docker/awesome-compose/tree/master/official-documentation-samples/django/'

Now we can create the docker-compose.yml

services:
  db:
    image: postgres
    container_name: postgresql-docker
    restart: on-failure
    networks:
      - database-network
    volumes:
      - ./postgres_data:/var/lib/postgresql/data
    ports:
      - ${POSTGRES_HOST_PORT}:${POSTGRES_HOST_PORT}
    environment:
      - POSTGRES_DB=${POSTGRES_DB}
      - POSTGRES_USER=${POSTGRES_USER}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
  backend:
    build: .
    container_name: django-docker
    restart: on-failure
    networks:
      - database-network
    command: python manage.py runserver 0.0.0.0:${BACKEND_PORT}
    volumes:
      - .:/code
    ports:
      - ${BACKEND_PORT}:${BACKEND_PORT}
    environment:
      - POSTGRES_DB=${POSTGRES_DB}
      - POSTGRES_USER=${POSTGRES_USER}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
      - POSTGRES_HOST_PORT=${POSTGRES_HOST_PORT}
    depends_on:
      - db
networks:
  database-network:
    driver: bridge

We have defined two containers db & backend, this names should be allusive to what the container means to the developer and those names seam to fit.
The db will use the standard postgres image, but you can specify any version you want like this:

image: postgres:15.1

The container-name is just for meta-data so it's not a random name, we also want to restart on failure, and we will need containers to talk to the DataBase, so we create an internal network database-network.
We also want the DataBase to be persistent so we will set up a volume for it.

Since the volume will setup the postgres_data in the same folder I will add this to the .gitignore so it's not pushed to my repo.
Also you should include .env in the .gitignore

The .env should contain all the variables that you used for the docker-compose.yml
And .env.example is a template to show people how they should set up the .env

Now we are ready to start the Django project.
sudo docker compose run backend django-admin startproject portfolio42 .

If you are on linux, you might need to change the ownership of manage.py in order to start using the script.
sudo chown -R $USER:$USER composeexample manage.py

Now you have to set-up the PostgreSQL database for the Django project.

Go to <django_project>/settings.py and change the following:
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': os.environ.get('POSTGRES_DB'),
        'USER': os.environ.get('POSTGRES_USER'),
        'PASSWORD': os.environ.get('POSTGRES_PASSWORD'),
        'HOST': 'postgresql-docker',
        'PORT': os.environ.get('POSTGRES_HOST_PORT'),
    }
}

Now you can run: 
docker compose up

And go to the localhost:<BACKEND_PORT>
At this point you are good to go, just start developing your application.

I decided to add a local workflow CI testing integrated with Github.
This will help developers run the test suite every time they do a Pull Request, and this way they can see if their code is passing or not.

Just create a the folder:
mkdir -p .github/workflows

Now create a file:
touch .github/workflows/django.yml 

In that file put the following code:
name: Django CI

on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]

jobs:
  build:

    # runs-on: ubuntu-latest
    runs-on: self-hosted
    strategy:
      max-parallel: 4
      matrix:
        python-version: [3.8]

    steps:
    - uses: actions/checkout@v3
    - name: Set up Python ${{ matrix.python-version }}
      uses: actions/setup-python@v3
      with:
        python-version: ${{ matrix.python-version }}
    - name: Install Dependencies
      run: |
        python3 -m pip install --upgrade pip
        pip install -r requirements.txt
    - name: Run Tests
      run: |
        python3 manage.py test --settings=portfolio42.unittest_settings

In this case I placed:
runs-on: self-hosted
Because I host my own server to test, but you can also out an ubuntu machine which github provides. But that will be slower.

What If I want to use React?

I will set up a React app for the front end, so my django application will actually only serve as an API.
In this case we want to divide the project in two: backend & frontend.

npx create-react-app frontend
mkdir backend
mv requirements.txt manage.py Dockerfile <your_django_project> backend/

That way the root looks something like this:
Now we should change the docker-compose a little, because our container image change location:

services:
  db:
    image: postgres
    container_name: postgresql-docker
    restart: on-failure
    networks:
      - database-network
    volumes:
      - ./postgres_data:/var/lib/postgresql/data
    ports:
      - ${POSTGRES_HOST_PORT}:${POSTGRES_HOST_PORT}
    environment:
      - POSTGRES_DB=${POSTGRES_DB}
      - POSTGRES_USER=${POSTGRES_USER}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
  backend:
    build: backend/.
    container_name: django-docker
    restart: on-failure
    networks:
      - database-network
    command: python backend/manage.py runserver 0.0.0.0:${BACKEND_PORT}
    volumes:
      - .:/code
    ports:
      - ${BACKEND_PORT}:${BACKEND_PORT}
    environment:
      - POSTGRES_DB=${POSTGRES_DB}
      - POSTGRES_USER=${POSTGRES_USER}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
      - POSTGRES_HOST_PORT=${POSTGRES_HOST_PORT}
    depends_on:
      - db
networks:
  database-network:
    driver: bridge

Your django.yml for CI should also change:

name: Django CI

on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]

jobs:
  build:

    # runs-on: ubuntu-latest
    runs-on: self-hosted
    strategy:
      max-parallel: 4
      matrix:
        python-version: [3.8]

    steps:
    - uses: actions/checkout@v3
    - name: Set up Python ${{ matrix.python-version }}
      uses: actions/setup-python@v3
      with:
        python-version: ${{ matrix.python-version }}
    - name: Install Dependencies
      run: |
        cd backend
        python3 -m pip install --upgrade pip
        pip install -r requirements.txt
    - name: Run Tests
      run: |
        cd backend
        python3 manage.py test --settings=portfolio42.unittest_settings

Now you should be able to run the project with their respective docker containers, also in the future you might want to add another container for the REACT, this could be for the production hosting of the files, or just to run a node server for the react development.


I hope you enjoyed this tutorial, good luck Dockerizing your applications!