Django 4: Adding a database to our stack
Tags: docker, django, python, timescaledb
Published: 2024-03-13 05:15:01

> _NOTE: This article was initially posted on my Substack, at https://andresalvareziglesias.substack.com/_

Hi everyone!

Now, we can serve both dynamic and static files, and we have a single entry point for our app. The  next step is to add a DB engine to our stack.

Let 's get started!

## Articles in this series

- Chapter 1: [Let the journey start](https://andresalvareziglesias.substack.com/p/let-the-journey-start-with-python-docker-and-ai?utm_source=post-toc)
- Chapter 2: [Create a containerized Django app with Gunicorn and Docker](https://andresalvareziglesias.substack.com/p/create-a-containerized-django-app?utm_source=post-toc)
- Chapter 3: [Serve Django static files with NGINX](https://andresalvareziglesias.substack.com/p/serve-django-static-files-with-nginx?utm_source=post-toc)
- Chapter 4: [Adding a database to our stack](https://andresalvareziglesias.substack.com/p/adding-a-database-to-our-stack?utm_source=post-toc)
- Chapter 5: [Applications and sites](https://andresalvareziglesias.substack.com/p/django-5-applications-and-sites)
- Chapter 6: [Using the Django ORM](https://andresalvareziglesias.substack.com/p/django-6-using-the-django-orm)
- Chapter 7: [Users login, logout and register](https://andresalvareziglesias.substack.com/p/django-7-users-login-logout-and-register)

## Why PostgreSQL/TimescaleDB?

I choose TimescaleDB (a PostgreSQL extension for time series) for the following reasons:

- I'm already familiar with PostgreSQL, an almost indestructible DB engine, tested in production environments for years.
- I want to learn how to use the TimescaleDB extension
- Because... why not?

## Add TimescaleDB container

Create a new directory for the new container:

```
cd tic-magical-line
mkdir db
```

Inside the db folder, create the same three files than in other containers:

```
cd tic-magical-line/db
touch Dockerfile
touch environment.env
touch requirements.txt
```

Write environment.env required variables (write your own user/password, this is used only for demo purposes):

```
POSTGRES_USER=tic
POSTGRES_PASSWORD=drag0n
PGUSER=tic
```

Write the following content in Dockerfile:

```
FROM timescale/timescaledb-ha:pg16
```

## Add the container to docker-compose file

Modify the docker-compose file adding dependencies between containers and the new TimescaleDB container. We are also adding a health-check to the database container:

```
version: "3.8"

services:
 app:
   build:
     context: ./
     dockerfile: ./app/Dockerfile
   container_name: app
   hostname: app
   restart: always
   volumes:
     - ./src/:/usr/src/app/
   depends_on:
     db:
       condition: service_healthy
   env_file:
     - ./app/environment.env
   networks:
     django_net:
       ipv4_address: 10.20.30.1

 server:
   build:
     context: ./
     dockerfile: ./server/Dockerfile
   container_name: server
   hostname: server
   restart: always
   depends_on:
     db:
       condition: service_healthy
   env_file:
     - ./server/environment.env
   ports:
     - 8080:8080
   networks:
     django_net:
       ipv4_address: 10.20.30.2

 db:
   build:
     context: ./
     dockerfile: ./db/Dockerfile
   container_name: db
   hostname: db
   restart: always
   env_file:
     - ./db/environment.env
   volumes:
     - ./db/init.sql:/docker-entrypoint-initdb.d/init.sql
   healthcheck:
     test: [ "CMD-SHELL", "pg_isready", "-d", "ticmagicalline" ]
     interval: 30s
     timeout: 10s
     retries: 3
   networks:
     django_net:
       ipv4_address: 10.20.30.3

networks:
 django_net:
   ipam:
     config:
       - subnet: 10.20.30.0/24
         gateway: 10.20.30.254
```

As you can see, one of the lines of the DB container specifies an SQL file to execute at container start. This is very useful to initialize the DB. We need to create the database that the Django app will use.

Create the SQL file:

```
cd tic-magical-line/db
touch init.sql
```

Write the following content:

```
CREATE DATABASE ticmagicalline;
```

## Configure the database in Django

Edit settings.py inside Django app source code folder, and set the following variables to configure the database backend:

```
DATABASES = {
   'default': {
       "ENGINE": "django.db.backends.postgresql",
       "NAME": "ticmagicalline",
       "USER": "tic",
       "PASSWORD": "drag0n",
       "HOST": "10.20.30.3",
       "PORT": "5432",
   }
}
```

NOTE: Write your chosen password in this code block

After configuring the backend and creating the database, we need to populate it with the default tables and data. This is done with the manage.py command, inside the container:

- Get the Django app container ID: docker ps | grep tic-magical-line_app
- Enter in the container shell: docker exec -it 5e1d486aff24 /bin/bash
- Go to app folder: cd /
- Execute migration: python manage.py migrate
- Exit from the shell: exit

Also, we need to create an admin user:

- Get the Django app container ID: docker ps | grep tic-magical-line_app
- Enter in the container shell: docker exec -it 5e1d486aff24 /bin/bash
- Go to app folder: cd /
- Create user: python manage.py createsuperuser
- Exit from the shell: exit

We can automate all these changes using the main app Dockerfile. Edit it with this content:

```
FROM python:3.12.2-bookworm

ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

RUN pip install --upgrade pip
COPY ./app/requirements.txt .
RUN pip install -r requirements.txt
COPY ./app/entrypoint.sh .
RUN chmod +x entrypoint.sh

COPY ./app/src/ticmagicalline .

ENTRYPOINT [ "/entrypoint.sh" ]
```

Then, create the new entrypoint.sh file: with:

```
#!/bin/sh

echo "Executing Django DB migration..."
python manage.py migrate

echo "Creating Django administrator..."
python manage.py createsuperuserwithpassword \
   --username $DJANGO_ADMIN_USERNAME \
   --password $DJANGO_ADMIN_PASSWORD \
   --email $DJANGO_ADMIN_EMAIL \
   --preserve

echo "Initial tasks finalized!"

gunicorn ticmagicalline.wsgi:application --bind 0.0.0.0:8081
```

Add the new variables to environment.env file:

```
DJANGO_ADMIN_USERNAME=tic
DJANGO_ADMIN_EMAIL=tic@fake.com
DJANGO_ADMIN_PASSWORD=drag0n
```

And add the new dependency to requirements.txt:

```
Django
gunicorn
psycopg2-binary
django-createsuperuserwithpassword
```

Don't forget to edit the Django app settings to enable the new  createsuperuserwithpassword extension adding the following line to settings.py:

```
INSTALLED_APPS += ("django_createsuperuserwithpassword", )
```

## Rebuild the images and execute again

After all these changes, we need to rebuild the new images with:

```
docker-compose stop
docker-compose build
```

After the data population, start the cluster again with:

```
docker-compose up
```

Now, we can test the database integration login into the Django admin site, with this link:

[http://localhost:8080/admin/login/?next=/admin/](http://localhost:8080/admin/login/?next=/admin/) 

Use the username and password defined in the previous step. And voilá! We are now inside our Django app admin site!

![Django app admin site](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h0frmf6bjlnt4iovfpbl.png)

## About the list

Among the Python and Docker posts, I will also write about other related topics (always tech and programming topics, I promise... with the fingers crossed), like:

- Software architecture
- Programming environments
- Linux operating system
- Etc.

If you found some interesting technology, programming language or whatever, please, let me know! I'm always open to learning something new!

## About the author

I'm Andrés, a full-stack software developer based in Palma, on a personal journey to improve my coding skills. I'm also a self-published fantasy writer with four published novels to my name. Feel free to ask me anything!