Language
日本語
English

Caution

JavaScript is disabled in your browser.
This site uses JavaScript for features such as search.
For the best experience, please enable JavaScript before browsing this site.

  1. Home
  2. Docker Dictionary
  3. compose Example: Django + PostgreSQL

compose Example: Django + PostgreSQL

This page covers the configuration for connecting a Django app and PostgreSQL using Docker Compose. Because migrations must run after the db service is fully started, the timing is controlled by combining depends_on with a command, or by using a separate entrypoint script.

Syntax

# -----------------------------------------------
#  Basic configuration for Python (Django) + PostgreSQL
# -----------------------------------------------

# services:
#   db:                         → PostgreSQL service definition
#     image: postgres:16        → PostgreSQL version to use
#     environment:              → Initial database setup via environment variables
#       POSTGRES_DB: appdb
#       POSTGRES_USER: appuser
#       POSTGRES_PASSWORD: secret
#     volumes:
#       - db_data:/var/lib/postgresql/data  → Persist data in a named volume
#     healthcheck:              → Health check to confirm PostgreSQL is ready
#       test: ["CMD-SHELL", "pg_isready -U appuser -d appdb"]
#       interval: 5s
#       retries: 5
#
#   web:                        → Django app service definition
#     build: .                  → Build from the Dockerfile in the current directory
#     command: >
#       sh -c "python manage.py migrate &&
#              python manage.py runserver 0.0.0.0:8000"
#       → Run migrations and then start the development server.
#     environment:
#       DATABASE_URL: postgres://appuser:secret@db:5432/appdb
#     ports:
#       - "8000:8000"
#     volumes:
#       - .:/app                → Mount host source code into the container
#     depends_on:
#       db:
#         condition: service_healthy  → Start only after db's health check passes
#
# volumes:
#   db_data:                    → Named volume for PostgreSQL data persistence

Syntax Reference

Syntax / Config KeyDescription
image: postgres:16Uses the official PostgreSQL version 16 image from Docker Hub.
POSTGRES_DBSpecifies the name of the database to create automatically on the first container startup.
POSTGRES_USERSpecifies the PostgreSQL superuser name.
POSTGRES_PASSWORDSpecifies the password for the above user. In production, manage it with secrets or a .env file.
healthcheckPeriodically checks the service's readiness. Using pg_isready allows waiting until PostgreSQL is ready to accept connections.
depends_on: condition: service_healthyStarts the current service only after the dependency service's health check succeeds. A simple depends_on can only control container startup order and does not guarantee DB readiness.
commandOverrides the CMD in the Dockerfile with the command to run. Migrations and startup can be combined here.
python manage.py migrateApplies all pending migrations. Required on first startup and after changes to models.py.
DATABASE_URLA database connection string read by django-environ or dj-database-url. Written in the format postgres://user:password@host:port/dbname.
volumes: - .:/appMounts the host project directory to /app inside the container. Code changes are reflected immediately.
Named volume (db_data)Preserves database data even when containers are removed or recreated. Can be explicitly deleted with docker compose down -v.

Sample Code

docker-compose.yml — Django + PostgreSQL configuration
# -----------------------------------------------
#  docker-compose.yml
#  Configuration for Django app (web) and PostgreSQL (db)
# -----------------------------------------------

services:
  db:
    image: postgres:16
    environment:
      POSTGRES_DB: nerv_db
      POSTGRES_USER: ikari_gendo
      POSTGRES_PASSWORD: seele0001
    volumes:
      - db_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ikari_gendo -d nerv_db"]
      interval: 5s
      timeout: 5s
      retries: 5

  web:
    build: .
    command: >
      sh -c "python manage.py migrate &&
             python manage.py runserver 0.0.0.0:8000"
    environment:
      DATABASE_URL: postgres://ikari_gendo:seele0001@db:5432/nerv_db
      DJANGO_SECRET_KEY: evangelion-secret-key
      DEBUG: "True"
    ports:
      - "8000:8000"
    volumes:
      - .:/app
    depends_on:
      db:
        condition: service_healthy

volumes:
  db_data:
Dockerfile — for the Django app
# -----------------------------------------------
#  Dockerfile
#  Django app image based on Python 3.12
# -----------------------------------------------

FROM python:3.12-slim

# Set the working directory to /app.
WORKDIR /app

# Copy the dependency file first to leverage cache.
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy the entire application.
COPY . .
requirements.txt
# -----------------------------------------------
#  requirements.txt
# -----------------------------------------------

Django==5.0.4
psycopg2-binary==2.9.9   # PostgreSQL driver
dj-database-url==2.1.0   # Automatically parses the DATABASE_URL environment variable
Django settings.py — database configuration excerpt
# -----------------------------------------------
#  settings.py (excerpt)
#  Load DATABASE_URL with dj-database-url.
# -----------------------------------------------

import dj_database_url
import os

DATABASES = {
    'default': dj_database_url.config(
        default=os.environ.get('DATABASE_URL'),
        conn_max_age=600,
    )
}
Start up, run migrations, and verify operation
# -----------------------------------------------
#  Start containers and run migrations.
# -----------------------------------------------

# Build images and start in the background.
docker compose up --build -d

# Check the web service logs (confirm that migrations completed).
docker compose logs web

# Connect directly to PostgreSQL and verify (password: seele0001).
docker compose exec db psql -U ikari_gendo -d nerv_db -c "\dt"

# Stop containers (volumes are retained).
docker compose down
$ docker compose up --build -d
[+] Building 12.3s (10/10) FINISHED
[+] Running 3/3
 ✔ Network nerv_default  Created
 ✔ Container nerv-db-1   Healthy
 ✔ Container nerv-web-1  Started
$ docker compose logs web
nerv-web-1  | Operations to perform:
nerv-web-1  |   Apply all migrations: admin, auth, contenttypes, sessions
nerv-web-1  | Running migrations:
nerv-web-1  |   Applying contenttypes.0001_initial... OK
nerv-web-1  |   Applying auth.0001_initial... OK
nerv-web-1  |   Applying admin.0001_initial... OK
nerv-web-1  |   Applying sessions.0001_initial... OK
nerv-web-1  | Watching for file changes with StatReloader
nerv-web-1  | Performing system checks...
nerv-web-1  | System check identified no issues (0 silenced).
nerv-web-1  | Django version 5.0.4, using settings 'config.settings'
nerv-web-1  | Starting development server at http://0.0.0.0:8000/
nerv-web-1  | Quit the server with CONTROL-C.
$ docker compose exec db psql -U ikari_gendo -d nerv_db -c "\dt"
                  List of relations
 Schema |            Name            | Type  |    Owner
--------+----------------------------+-------+-------------
 public | auth_group                 | table | ikari_gendo
 public | auth_permission            | table | ikari_gendo
 public | auth_user                  | table | ikari_gendo
 public | django_admin_log           | table | ikari_gendo
 public | django_content_type        | table | ikari_gendo
 public | django_migrations          | table | ikari_gendo
 public | django_session             | table | ikari_gendo
(7 rows)
Add a custom model and run migrations
# -----------------------------------------------
#  Add a model to models.py and run migrations.
# -----------------------------------------------

# Define the model on the host (reflected immediately via volumes mount).
# Add the following to models.py:
#
#   from django.db import models
#
#   class Pilot(models.Model):
#       name = models.CharField(max_length=100)
#       unit = models.IntegerField()
#
#       def __str__(self):
#           return self.name

# Generate the migration file.
docker compose exec web python manage.py makemigrations

# Run the migrations.
docker compose exec web python manage.py migrate

# Register and verify data with the Django shell.
docker compose exec web python manage.py shell
$ docker compose exec web python manage.py makemigrations
Migrations for 'pilots':
  pilots/migrations/0001_initial.py
    - Create model Pilot
$ docker compose exec web python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, pilots, sessions
Running migrations:
  Applying pilots.0001_initial... OK
$ docker compose exec web python manage.py shell
Python 3.12.3 (main, Apr  9 2024, 08:09:14) [GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from pilots.models import Pilot
>>> Pilot.objects.create(name='Ikari Shinji', unit=1)
<Pilot: Ikari Shinji>
>>> Pilot.objects.create(name='Ayanami Rei', unit=0)
<Pilot: Ayanami Rei>
>>> Pilot.objects.create(name='Soryu Asuka', unit=2)
<Pilot: Soryu Asuka>
>>> Pilot.objects.all()
<QuerySet [<Pilot: Ikari Shinji>, <Pilot: Ayanami Rei>, <Pilot: Soryu Asuka>]>

Details

The most important point when connecting Django and PostgreSQL with Docker Compose is controlling migration timing. Using depends_on alone only guarantees container startup order and does not guarantee that PostgreSQL is ready to accept connections. By setting pg_isready in healthcheck and combining it with depends_on: condition: service_healthy, you can reliably ensure that Django starts only after the DB is ready.

In a development environment, writing python manage.py migrate && python manage.py runserver in command is convenient, but in production it is common practice to manage migrations and server startup explicitly in the Dockerfile's ENTRYPOINT script. Also, sensitive information such as POSTGRES_PASSWORD should be managed with a .env file or Docker Secrets and not hardcoded in docker-compose.yml.

Related pages: Docker Compose basics / volumes and data persistence / environment variables and .env files

Common Mistakes

Using depends_on without a healthcheck causes DB connection failures

depends_on: condition: service_started (including short-form syntax) only checks that the container process has started. It is common for Django to attempt to run migrations before PostgreSQL has finished initializing, resulting in a connection error. Handle this with a healthcheck using pg_isready combined with condition: service_healthy.

services:
  db:
    image: postgres:16
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ikari_gendo -d nerv_db"]
      interval: 5s
      retries: 5
  web:
    depends_on:
      db:
        condition: service_healthy

A volumes mount overwrites the COPY in the Dockerfile

Mounting the host directory into the container with volumes: - .:/app overwrites what was COPY-ed in the Dockerfile. If files such as node_modules or __pycache__ exist locally, they may be mixed into the container. Exclude them with .dockerignore or mount only a subpath.

Forgetting to run migrations leaves tables nonexistent

If command does not include python manage.py migrate, tables remain uncreated after the container starts. In production, either include migrations explicitly in an ENTRYPOINT script, or include them in command.

If you find any errors or copyright issues, please .