Baeldung Pro – Ops – NPI EA (cat = Baeldung on Ops)
announcement - icon

Learn through the super-clean Baeldung Pro experience:

>> Membership and Baeldung Pro.

No ads, dark-mode and 6 months free of IntelliJ Idea Ultimate to start with.

Partner – Orkes – NPI EA (cat=Kubernetes)
announcement - icon

Modern software architecture is often broken. Slow delivery leads to missed opportunities, innovation is stalled due to architectural complexities, and engineering resources are exceedingly expensive.

Orkes is the leading workflow orchestration platform built to enable teams to transform the way they develop, connect, and deploy applications, microservices, AI agents, and more.

With Orkes Conductor managed through Orkes Cloud, developers can focus on building mission critical applications without worrying about infrastructure maintenance to meet goals and, simply put, taking new products live faster and reducing total cost of ownership.

Try a 14-Day Free Trial of Orkes Conductor today.

1. Overview

When working with Django in a Dockerized environment, we need to ensure that database migrations are performed smoothly. However, it’s common to encounter a problem where Django fails to detect changes to models. In turn, these forces developers to recreate containers each time they modify the database schema.

In this tutorial, we’ll set up a simple Dockerized Django application and discuss how database migrations work within Docker. To demonstrate, we’ll work in the Linux environment.

2. Setting up a Dockerized Django Application

To discuss migrations within Docker, let’s use PostgreSQL and Docker Compose to set up a simple Django project. Let’s also make sure that Docker, Docker Compose, and Python are installed on our system.

2.1. Creating the Django Project

First, let’s create the project directory for our Django application and navigate into it:

$ mkdir dockerized-django-app && cd dockerized-django-app

After this, let’s set up the virtual environment:

$ python3 -m venv venv && source venv/bin/activate && pip install django psycopg2-binary

Now, we can create the Django project:

$ django-admin startproject myproject .

Here, we create the Django project myproject inside the dockerized-django-app project directory.

Next, let’s modify myproject/settings.py to use PostgreSQL:

from pathlib import Path
import os

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': os.getenv('POSTGRES_DB', 'mydatabase'),
        'USER': os.getenv('POSTGRES_USER', 'myuser'),
        'PASSWORD': os.getenv('POSTGRES_PASSWORD', 'mypassword'),
        'HOST': 'db',
        'PORT': '5432',
    }
}

We configure Django settings to enable working with PostgreSQL.

2.2. Set Up Dockerfile

In this step, let’s create a Dockerfile to define the Docker image for the Django application:

FROM python:3.9

WORKDIR /app

COPY . /app

RUN pip install --no-cache-dir -r requirements.txt

EXPOSE 8000

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

In addition, let’s create a requirements.txt file.

django
psycopg2-binary

So, we include the necessary dependencies in the requirements.txt file.

2.3. Set Up the docker-compose.yml File

Here, we create the docker-compose.yml file:

version: '3.9'
services:
  db:
    image: postgres
    restart: always
    environment:
      POSTGRES_DB: mydatabase
      POSTGRES_USER: myuser
      POSTGRES_PASSWORD: mypassword
    volumes:
      - postgres_data:/var/lib/postgresql/data

  web:
    build: .
    command: python manage.py runserver 0.0.0.0:8000
    volumes:
      - .:/app
    ports:
      - "8000:8000"
    depends_on:
      - db
    environment:
      DATABASE_URL: postgres://myuser:mypassword@db:5432/mydatabase

volumes:
  postgres_data:

This ensures that Docker Compose helps us manage the Django app and PostgreSQL database.

2.4. Start Services and Run Migrations

To start the services we run:

$ docker-compose up -d
...
Creating dockerized-django-app_db_1 ... done
Creating dockerized-django-app_web_1 ... done

Then, to run migrations:

$ docker-compose run web python manage.py migrate

This command creates the database schema inside the PostgreSQL container if the migration is successful.

3. Handling Model Changes and Migrations

Now, let’s see what happens when we add a new model.

3.1. Create a New Django App

In myproject directory, let’s add the books Django app:

$ docker-compose run web sh -c "python manage.py startapp books && ls -l && mv books myproject/"
Creating dockerized-django-app_web_run ... done
total 28
-rw-rw-r-- 1 root root  174 Feb 10 17:30 Dockerfile
drwxr-xr-x 3 root root 4096 Feb 12 10:59 books
-rw-rw-r-- 1 root root  514 Feb 10 18:12 docker-compose.yml
-rwxrwxr-x 1 root root  665 Feb 10 17:18 manage.py
drwxrwxr-x 3 root root 4096 Feb 12 10:54 myproject
-rw-rw-r-- 1 root root   23 Feb 10 17:38 requirements.txt
drwxrwxr-x 4 root root 4096 Feb 12 10:53 venv

This is what the command does:

  • sh -c – enables the execution of multiple commands inside the Docker container web
  • python manage.py startapp books – creates the books Django app
  • ls -l – lists the files to confirm the creation of books
  • mv books myproject/ – moves books into the myproject/ directory

Now, the books directory is also present locally in the myproject directory. Let’s proceed to register books in the inside the settings.py:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'myproject.books',
]

Inside settings.py, we modify INSTALLED_APPS.

Also, we need to ensure that the name holds the value ‘myproject.books’ in myproject/books/apps.py:

from django.apps import AppConfig

class BooksConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'myproject.books'

From here, we can proceed to define a new model.

3.2. Define a New Model

Once we create the app, let’s navigate to books/models.py and modify it:

class Book(models.Model):
    title = models.CharField(max_length=255)
    author = models.CharField(max_length=255)
    published_date = models.DateField()

    def __str__(self):
        return self.title

Here, we define the Book model.

3.3. Apply Migrations

After defining the Book model, let’s create and apply database migrations:

$ docker-compose run web python manage.py makemigrations books
Creating dockerized-django-app_web_run ... done
Migrations for 'books':
  myproject/books/migrations/0001_initial.py
    - Create model Book

This generates a new migration file inside the books/migrations/ folder.

Thereafter, let’s apply the migration:

$ docker-compose run web python manage.py migrate
Creating dockerized-django-app_web_run ... done
Operations to perform:
  Apply all migrations: admin, auth, books, contenttypes, sessions
Running migrations:
  Applying books.0001_initial... OK

Above, we update the PostgreSQL database schema.

3.4. Verify the Model

Let’s start by modifying the books/admin.py file to register the model:

from django.contrib import admin
from .models import Book

admin.site.register(Book)

The above modification enables us to add books and manage them through the UI. However, we need to add a superuser to see this addition:

$ docker-compose run web python manage.py createsuperuser
Creating dockerized-django-app_web_run ... done
Username (leave blank to use 'root'): samuel
Email address: [email protected]
Password: 
Password (again): 
Superuser created successfully.

Once this is done, let’s log into http://localhost:8000/admin/ and confirm that the Book model exists under the section Books.

3.5. The Migration Not Detected Issue

In a Dockerized Django project, it’s common for Django to fail to detect model changes. To be specific, makemigrations doesn’t generate new migration files. This can be a result of how Docker handles caching or even volumes.

For instance, if we modify the existing model books/models.py:

class Book(models.Model):
    title = models.CharField(max_length=255)
    author = models.CharField(max_length=255)
    published_date = models.DateField()
    isbn = models.CharField(max_length=13, unique=True) # New field

Then, we proceed to create migrations:

$ docker-compose run web python manage.py makemigrations books

If Django fails to detect the change, we can try a few troubleshooting steps.

Firstly, we can manually check whether Django detects the change:

$ docker-compose run web python manage.py makemigrations --dry-run

Django doesn’t recognize the model change if the command doesn’t display any output.

Secondly, we need to ensure the Django app is added to INSTALLED_APPS. In our project, we need to open settings.py and confirm that INSTALLED_APPS lists ‘myproject.books’.

Thirdly, we can check if the migration directory exists. We can check if books/migrations/ contains migration files don’t exist. If not, we can try:

$ docker-compose run web python manage.py makemigrations books

We can also try restarting containers:

$ docker-compose down && docker-compose up -d

Well, this removes outdated volumes and cached data.

Lastly, we can manually delete and recreate migrations:

$ rm -rf books/migrations/*

Above, we delete the content in books/migrations/. To follow this, we then recreate the migrations:

$ docker-compose run web python manage.py makemigrations books && docker-compose run web python manage.py migrate

These steps enable the proper detection of the model changes and the correct application of the migrations. For this reason, the database remains in sync with the Django models while running inside Docker.

4. Conclusion

In this article, we explored how to effectively handle database migrations using Docker Compose and PostgreSQL in a dockerized environment.

We worked on setting up a dockerized Django project, configuring PostgreSQL, and running migrations smoothly. By providing troubleshooting steps to tackle the issue of Django failing to detect model changes, we ensure database schema updates are applied correctly.

Now, we can maintain a smooth workflow when managing database changes in a containerized Django application. Properly handling migrations with Docker significantly improves the development experience, eliminating the need for container recreation, reducing downtime, and ensuring database consistency across different environments.

The code samples are available over on GitHub.