DEV Community

Andrés Álvarez Iglesias
Andrés Álvarez Iglesias

Posted on

Adding a database to our stack: Building a Multiplayer Tic Tac Toe with Python, Docker, and AI, chapter 4

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
Chapter 2: Create a containerized Django app with Gunicorn and Docker
Chapter 3: Serve Django static files with NGINX
Chapter 4: Adding a database to our stack

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Write the following content in Dockerfile:

FROM timescale/timescaledb-ha:pg16
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Write the following content:

CREATE DATABASE ticmagicalline;
Enter fullscreen mode Exit fullscreen mode

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",
   }
}
Enter fullscreen mode Exit fullscreen mode

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" ]
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Add the new variables to environment.env file:

DJANGO_ADMIN_USERNAME=tic
DJANGO_ADMIN_EMAIL=tic@fake.com
DJANGO_ADMIN_PASSWORD=drag0n
Enter fullscreen mode Exit fullscreen mode

And add the new dependency to requirements.txt:

Django
gunicorn
psycopg2-binary
django-createsuperuserwithpassword
Enter fullscreen mode Exit fullscreen mode

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", )
Enter fullscreen mode Exit fullscreen mode

Rebuild the images and execute again

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

docker-compose stop
docker-compose build
Enter fullscreen mode Exit fullscreen mode

After the data population, start the cluster again with:

docker-compose up
Enter fullscreen mode Exit fullscreen mode

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

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

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!

Top comments (0)