Dockerized Django and PostgreSQL Setup for REST API through DjangoRestFramework (DRF)

Posted by Ray on April 5, 2022

Updated Feb 14, 2024

This document is a guide for setting up a Dockerized Django with DjangoRestFramework (DRF) and PostgreSQL Database backend web application from scratch.

Pre-requisites

  1. Python
  2. Docker

Setting up Django and Postgresql in a virtual environment (pipenv)

***NOTE: Example project name is backend_api

1
2
3
4
5
6
7
8
9
10
11
12
13
# Create project folder
$ mkdir backend_api && cd backend_api

# Create pipenv with django and pyscopg2-binary installed
$ python3 -m pipenv install django psycopg2-binary django-environ
# if `No module named pipenv` error shows,
# install pipenv through `pip3 install pipenv`

# Enter virtual environment
$ python3 -m pipenv shell

# Check if django and psycopg2-binary is installed
$ pip freeze

When using macos, virtual environment will be stored in Users/<your_user>/.local/share/virtualenvs/

Creating an initial Django project

***NOTE: You are still inside the virtual environment

1
2
3
4
5
# Create the initial structure of a django project
(backend_api) $ django-admin startproject config .

# Run the server of the project
(backend_api) $ python manage.py runserver

Go to http://127.0.0.1:8000/ on your web browser, and you should see the Django welcome page.
***NOTE: DO NOT apply migrations yet

Creating the Dockerfile, docker-compose.yml, and .env file

Press ctrl + C on your keyboard to stop the local server

1
2
3
4
5
6
7
8
# Create the Dockerfile
$ touch Dockerfile

# Create the docker-compose.yml file
$ touch docker-compose.yml

# Create the .env file
$ touch .env

Open the Dockerfile on your text editor, or you can open it via terminal:

1
2
# Open Dockerfile via terminal
$ vim Dockerfile

and insert the code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# Pull base image
FROM python:your_python_version

# Update packages
RUN apt-get update \
    && apt-get upgrade -y \
    && apt-get install -y \
    build-essential \
    python3-dev \
    gcc \
    bash \
    # pip
    && pip3 install --upgrade pip

# Set Library Path
ENV LIBRARY_PATH=/lib:/usr/lib

# Set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

# Set work directory
WORKDIR /code

# Install dependencies
COPY Pipfile Pipfile.lock /code/
RUN pip install pipenv && pipenv install --system

# Copy project
COPY . /code/

(exit the vim by pressing the ESC button on the keyboard, then typing :x, then hit return)

Edit the python_version = "2.7" line of the Pipfile to match the python version in the Dockerfile



Open the docker-compose.yml file on your text editor, or you can open it via terminal:

1
2
# Open docker-compose.yml via terminal
$ vim docker-compose.yml

Insert the code, and edit and :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
version: '3.8'

services:
  web:
    build: .
    image: <project_name>-api:latest
    container_name: <project_name>_api
    restart: always
    command: python /code/manage.py runserver 0.0.0.0:8000
    env_file: .env
    volumes:
      - .:/code
    ports:
      - 8000:8000
    depends_on:
      postgres:
        condition: service_healthy
    stdin_open: true
    tty: true

  postgres:
    container_name: <project_name>_pgdb
    image: postgres:<postgres_version>  # LTS PSQL version as of writing: 16
    restart: always
    env_file: .env
    environment:
      - POSTGRES_DB=${POSTGRES_DB}
      - POSTGRES_USER=${POSTGRES_USER}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
      - POSTGRES_PORT=5432
    ports:
      - 5432:5432
    volumes:
      - ./_data/postgres:/var/lib/postgresql/data
      - ./docker/_postgres/scripts/create_test_db.sql:/docker-entrypoint-initdb.d/docker_postgres_init.sql
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -d postgres -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5

(exit the vim by pressing the ESC button on the keyboard, then typing :x, then hit return)

Edit the .env file with the following contents, and replace the values as needed:

# PostgreSQL
POSTGRES_HOST=postgres
POSTGRES_NAME=postgres
POSTGRES_DB=postgres
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
POSTGRES_PORT=5432

# Settings
DJANGO_SECRET_KEY=''
DJANGO_DEBUG=True

Edit config/settings.py with the following configurations:

  1. Insert the following code below the from pathlib import Path line: ```python from pathlib import Path import environ # new import os # new

initialize environment variables # new

env = environ.Env() # new environ.Env.read_env() # new

1
2
3
4
2. Copy the SECRET_KEY and paste it into the `.env` file. Replace this line with:
```python
SECRET_KEY = env("DJANGO_SECRET_KEY")
  1. Replace DEBUG = TRUE with
    1
    
    DEBUG = env("DJANGO_DEBUG")
    
  2. Replace
    1
    2
    3
    4
    5
    6
    
    DATABASES = {
     'default': {
         'ENGINE': 'django.db.backends.sqlite3',
         'NAME': BASE_DIR / 'db.sqlite3',
     }
    }
    

    with

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    DATABASES = {
     "default": {
         "ENGINE": "django.db.backends.postgresql",
         "HOST": env("POSTGRES_HOST"),
         "NAME": env("POSTGRES_NAME"),
         "USER": env("POSTGRES_USER"),
         "PASSWORD": env("POSTGRES_PASSWORD"),
         "PORT": env("POSTGRES_PORT"),
     }
    }
    

Verify if configuration file is good to go:

1
$ docker-compose config

Build the image and run the containers by:

1
2
# Build image and run the container
$ docker-compose up --build

***NOTE: Be sure to stop any docker container using the port 8000 so you don’t get a Bindfor 0.0.0.0:8000 failed: port is already allocated error.

Go to http://127.0.0.1:8000/ on your web browser, and you should see the Django welcome page.

Adding Custom User Model

Open another terminal (terminal2) on the same folder directory as the root project.
***NOTE: The project docker instance should be running on the other terminal

Create an accounts app within docker

1
2
3
# Create `accounts` app within docker.
# ***NOTE 'backend_api_web' is replaced by the name of your web container
$ docker exec backend_api_web python manage.py startapp accounts

Open up accounts/models.py and insert the code:

1
2
3
4
5
6
7
# accounts/models.py
from django.contrib.auth.models import AbstractUser
from django.db import models


class CustomUser(AbstractUser):
    pass

Open up config/settings.py and insert the new installed app on the list of installed_apps, and add the AUTH_USER_MODEL on the bottom of the file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# config/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    # Local
    'accounts', # new
]
...
AUTH_USER_MODEL = 'accounts.CustomUser' # new

Create the migrations of the accounts app through terminal2, and migrate the changes:

1
2
3
4
5
# Make migrations to the `accounts` app
$ docker exec backend_api_web python manage.py makemigrations accounts

# Migrate changes
$ docker exec backend_api_web python manage.py migrate

Create Custom User Admin

Open accounts/admin.py, and insert the code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# accounts/admin.py
from django.contrib import admin
from django.contrib.auth import get_user_model
from django.contrib.auth.admin import UserAdmin


CustomUser = get_user_model()


class CustomUserAdmin(UserAdmin):
    model = CustomUser
    list_display = ['email', 'username',]

admin.site.register(CustomUser, CustomUserAdmin)

Create a superuser account by:

1
2
# Create a superuser through docker
$ docker exec backend_api_web python manage.py createsuperuser

Input necessary details.

Restart the server (from terminal1), and navigate to Go to http://127.0.0.1:8000/admin. Input your new credentials, and you should see your credentials on the Users section under the Accounts.

To test use model, add the following test cases to accounts/tests.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# accounts/tests.py
from django.contrib.auth import get_user_model
from django.test import TestCase


class CustomUserTests(TestCase):

    def test_create_user(self):
        User = get_user_model()
        user = User.objects.create_user(
            username='will',
            email='will@email.com',
            password='testpass123'
        )
        self.assertEqual(user.username, 'will')
        self.assertEqual(user.email, 'will@email.com')
        self.assertTrue(user.is_active)
        self.assertFalse(user.is_staff)
        self.assertFalse(user.is_superuser)

    def test_create_superuser(self):
        User = get_user_model()
        admin_user = User.objects.create_superuser(
            username='superadmin',
            email='superadmin@email.com',
            password='testpass123'
        )
        self.assertEqual(admin_user.username, 'superadmin')
        self.assertEqual(admin_user.email, 'superadmin@email.com')
        self.assertTrue(admin_user.is_active)
        self.assertTrue(admin_user.is_staff)
        self.assertTrue(admin_user.is_superuser)

To run tests, type python manage.py test inside docker container.

Install djangorestframework

Using terminal2, install djangorestframework application by:

1
2
3
4
5
# Installing djangorestframework
$ docker exec backend_api_web pipenv install djangorestframework

# when inside docker container, use the command:
$ pipenv install djangorestframework

Add this newly installed app to config/settings.py

1
2
3
4
5
6
7
8
9
10
11
# config/settings.py
INSTALLED_APPS = [
    ...
    'django.contrib.staticfiles',

    # Third-party
    'rest_framework',  # new

    # Local
    'accounts',
]

Stop (ctrl + c) and rebuild the docker container (using terminal1).

1
2
# Rebuild and run docker container
$ docker-compose up --build

Setting timezone

1
2
3
# config/settings.py
...
TIME_ZONE = "Asia/Manila"  

Useful code snippets

Python

1
2
3
4
5
6
7
8
9
10
11
12
# Create pipenv and install packages
$ python3 -m pipenv install django psycopg2-binary

# Enter virtual environment
$ python3 -m pipenv shell

# Install python package outside docker container 
$ docker exec <container_name> pipenv install <package>

# Install python package inside docker container 
$ docker exec -it <container_name> bash
$ pipenv install <package>

Docker

1
2
3
4
5
6
7
8
# Build image and run container
$ docker-compose up --build

# Run container
$ docker-compose up

# Run container on the background
$ docker-compose up -d

Run commands inside docker container

1
2
3
4
5
# Commands flow
$ docker exec <container_name> python manage.py <commands>

# Enter docker container
$ docker exec -it <name> bash

Common Django Commands

1
2
3
4
5
6
7
8
9
10
11
# Create app
$ docker exec <container_name> python manage.py startapp <app_name>

# Make migrations to a specific app
$ docker exec <container_name> python manage.py makemigrations <app_name>

# Migrate changes
$ docker exec <container_name> python manage.py migrate

# Create a superuser outside docker container
$ docker <container_name> python manage.py createsuperuser

Resources

  1. Django Rest Framework x React Series
  2. dj-rest-auth Documentation
  3. dj-rest-auth Package Review