How to dockerize a Django, Preact, and PostgreSQL Application

  • A photo of Charlie Macnamara By Charlie Macnamara
  • on Feb 20, 2024
 

During my recent deep dive into new technologies, I found the classic issues of integrating numerous tech tools effectively. I've written about my experiences to save you the trouble I had.

One essential component I've looked into is using Docker to implement containerization. While the initial setup takes a little longer, it significantly simplifies and optimizes your technological stack.

Prerequisites

To follow along with this tutorial, make sure you have the following:

We'll start a Django application and then a Preact application, containerize them both, run the containers, and then create an API to ensure the stack works correctly.

Getting started

To start, create an empty folder; we'll name ours django-preact-docker and navigate to this folder from the terminal.

We'll follow the first steps of creating a Django application - creating a virtual environment, activating it, and then installing Django.

(If virtualenv is not installed, run pip install virtualenv)

Run these commands in the terminal:

virtualenv venv
source ./venv/bin/activate

Then, with our virtual environment running, install Django along with some other dependencies:

pip install django django-cors-headers psycopg2-binary

Now, we can create a new Django project and get it running on our local server.

Setting Up Django

From the terminal, navigate to django-preact-docker, and run:

django-admin startproject backend

The above command creates a new folder, backend. Navigate into this folder:

cd backend

And then start the project:

python manage.py runserver

You can test if the server is running by going to: http://127.0.0.1:8000/

a screenshot showing the successful test server

Finally, create a requirements file that will hold our requirements for the project. While in django-preact-docker/backend, run:

pip freeze > requirements.txt

Setting up our Preact application

Head back to our root folder django-preact-docker.

In the terminal, run:

npm init preact

When prompted, change the project directory to "frontend," then hit enter for the rest of the options.

Once installed, from the terminal, navigate into django-preact-docker/frontend and run the following command to set up a development server:

npm run dev

Once the server has started, it will print a local development URL to open in your browser. Check if this is working before moving on!

a screenshot showing a successful precat response

Containerizing

Next, we must create configuration files, so Docker knows what to do.

Containerize Django

Navigate to django-preact-docker/backend and create a new file:

touch .dockerignore

Open the file and add the following:

# The file may be hidden on Finder; the default macOS shortcut is Shift-Command-. to show hidden files
venv
env
.env
Dockerfile

Next, run:

touch Dockerfile

Open it and add:

FROM python:3.8-alpine

ENV PYTHONUNBUFFERED 1

WORKDIR /app/backend

COPY requirements.txt /app/backend/

RUN apk add --update --no-cache postgresql-dev gcc python3-dev musl-dev

RUN pip install -r requirements.txt

COPY . .

CMD [ "python", "manage.py", "runserver", "0.0.0.0:8000" ]

In the above code:

  • FROM specifies the parent image that we'll be using
  • ENV sets the environment variable PYTHONUNBUFFERED to "1" to give us real-time log output.
  • WORKDIR specifies the working directory within the container.
  • COPY the requirements file to the working directory and later install the requirements.
  • RUN, install some more dependencies for psycopg-2,
  • COPY the content of our backend to the Docker container
  • The starting command for our container

Containerize Preact

Go to django-preact-docker/frontend and create a new file:

touch .dockerignore

Open the file in your text editor and add the following:

node_modules
npm-debug.log
Dockerfile
yarn-error.log

Go back to the terminal in frontend and create another Dockerfile:

touch Dockerfile

Open and edit "Dockerfile" to contain:

FROM node:16-alpine

WORKDIR /app

COPY package*.json ./

RUN npm install

COPY . .

EXPOSE 5173

CMD ["npm", "run", "dev"]

Lastly, we must add the new npm run dev script. Head to frontend/package.json and change scripts too:

"scripts": {
  "dev": "vite --host 0.0.0.0",
}

Packaging our applications with Docker Compose

Next, we must create a configuration file to run our two Docker containers together. In the main folder, django-preact-docker, create a new file called docker-compose.yml:

touch docker-compose.yml

Open it and edit it to contain the following:

version: '3.9'

services:
  db:
    image: postgres:14-alpine
    ports:
      - '5432:5432'
    environment:
      - POSTGRES_PASSWORD=postgres
      - POSTGRES_USER=postgres
      - POSTGRES_DB=postgres
    volumes:
      - ./data/db:/var/lib/postgresql/data/

  frontend:
    build:
      context: ./frontend
      dockerfile: Dockerfile
    ports:
      - '5173:5173'
    volumes:
      - ./frontend:/app/frontend
    depends_on:
      - backend

  backend:
    build:
      context: ./backend
      dockerfile: Dockerfile
    environment:
      - POSTGRES_PASSWORD=postgres
      - POSTGRES_USER=postgres
      - POSTGRES_DB=postgres
    ports:
      - '8000:8000'
    volumes:
      - ./backend:/app/backend
    depends_on:
      - db

The Docker Compose file tells Docker how the different containers work together.

Build the containers

Go to the root django-preact-docker from the terminal and run the following command (this may take a while):

docker-compose build

Once complete, you should see the images in Docker Desktop.

You can then run the containers with the following:

docker-compose up

After this, the servers are accessible at the ports:

  • 5173 for frontend
  • 8000 for backend
  • 5432 for the database

You should check that they're running correctly first before moving on.

Press Ctrl + C or run docker-compose down to stop the containers.

Additional setup

We need to change a few settings,

Navigate to the backend folder and change settings.py to:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'postgres',
        'USER': 'postgres',
        'PASSWORD': 'postgres',
        'HOST': 'db',
        'PORT': '5432',
    }
}

Also, we need to change CORS further below in settings.py. The CORS configuration allows our different applications to communicate across different domains in the web browser:

INSTALLED_APPS = [
    'corsheaders',  # add this
]

MIDDLEWARE = [
    ...,
    "corsheaders.middleware.CorsMiddleware",
    "django.middleware.common.CommonMiddleware",
    ...,
]

CORS_ALLOW_ALL_ORIGINS = True

Testing the stack works together

Finally, we'll build a simple response code to send data from the Django backend and ensure everything works together.

In backend/backend, create a new file, views.py, and paste the following:

from django.http import JsonResponse

def get_text(request):
    text = "All operational from the Django backend!"

    data = {
        'text': text,
    }

    return JsonResponse(data)

Then open urls.py and add these two lines:

from .views import get_text

And in URL patterns:

urlpatterns = [
    # other code
    path('test/', get_text),
]
from django.contrib import admin
from django.urls import path
from .views import get_text

urlpatterns = [
  path('admin/', admin.site.urls),
  path('test/', get_text),
]

You can now see this text on http://localhost:8000/test/.

To display it in our frontend, navigate to frontend/src/index.jsx and change it to the following:

import { render } from 'preact';
import preactLogo from './assets/preact.svg';
import './style.css';
import { useState, useEffect } from 'react';

export function App() {
    const [text, setText] = useState(null);

    useEffect(() => {
        fetch('http://127.0.0.1:8000/test/')
            .then(res => res.json())
            .then(data => {
                setText(data.text);
            });
    });

    return (
        <div>
            <h1>An unbeatable tech stack</h1>
            <a href="https://preactjs.com" target="_blank">
                <img src={preactLogo} alt="Preact logo" height="160" width="160" />
            </a>
            <p>Running Preact, Django, Postgres, and Docker</p>
            <p>{text}</p>
        </div>
    );
}

render(<App />, document.getElementById('app'));

Refresh and reload, and you should see a working Preact front page!

Conclusion

By containerizing your technology stack with Docker, you've taken a significant step toward seamless integration and enhanced efficiency. While the initial setup might have required some effort, the benefits are clear.

Now that you've containerized your tech stack, including Django, Preact, and Postgres, you can explore, innovate, and navigate the ever-evolving tech landscape more effectively—a pivotal moment in your tech journey. Embrace the possibilities, and may your adventures be characterized by streamlined integration and boundless creativity!

Get the Honeybadger newsletter

Each month we share news, best practices, and stories from the DevOps & monitoring community—exclusively for developers like you.
    author photo
    Charlie Macnamara
    An advertisement for Honeybadger that reads 'Turn your logs into events.'

    "Splunk-like querying without having to sell my kidneys? nice"

    That’s a direct quote from someone who just saw Honeybadger Insights. It’s a bit like Papertrail or DataDog—but with just the good parts and a reasonable price tag.

    Best of all, Insights logging is available on our free tier as part of a comprehensive monitoring suite including error tracking, uptime monitoring, status pages, and more.

    Start logging for FREE
    Simple 5-minute setup — No credit card required