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
- Python
- 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
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:
- 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")
- Replace
DEBUG = TRUE
with1
DEBUG = env("DJANGO_DEBUG")
- 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