DEV Community

Python-T Point
Python-T Point

Posted on • Originally published at pythontpoint.in

⚙️ Deploying a dockerized Django Rest Framework with Postgres for production

Dockerizing Django REST Framework with PostgreSQL for production can be done with either a single‑stage Dockerfile that bundles the entire Python environment or a multi‑stage Dockerfile that separates build‑time dependencies from the runtime image. The single‑stage approach typically yields an image larger than 1 GB; the multi‑stage approach can shrink the final image to under 150 MB. Both methods produce a dockerized django rest framework with postgres deployment, but the resource consumption and startup time differ substantially.

📑 Table of Contents

  • 🐳 Dockerfile — Building a Lean Image
  • 🗄️ Docker Compose — Orchestrating Services
  • 📦 Service Definitions — Postgres
  • 🔧 Django Service — Application
  • ⚙️ Django Settings — Connecting to Postgres
  • 🔐 Secrets Management — Environment Variables
  • 🚀 Production Runtime — Process Management
  • 📊 Monitoring & Logging — Observability
  • 🟩 Final Thoughts
  • ❓ Frequently Asked Questions
  • How do I run database migrations in this Docker setup?
  • Can I replace PostgreSQL with another database without rebuilding the image?
  • What is the recommended way to handle static files in this container?
  • 📚 References & Further Reading

🐳 Dockerfile — Building a Lean Image

A multi‑stage Dockerfile removes build‑time packages after the required wheels are installed, leaving only the runtime dependencies.

# Dockerfile
# ---------- Build stage ---------
FROM python:3.12-slim AS builder
ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1
WORKDIR /app
COPY requirements.txt .
RUN pip install -upgrade pip && \ pip install -no-cache-dir -r requirements.txt # ---------- Runtime stage ---------
FROM python:3.12-slim
WORKDIR /app
COPY -from=builder /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
COPY . .
CMD ["gunicorn", "myproject.wsgi:application", "--bind", "0.0.0.0:8000"]
Enter fullscreen mode Exit fullscreen mode

What this does:

  • builder stage: installs only compile‑time dependencies and produces a wheel cache.
  • runtime stage: copies the pre‑installed site‑packages, avoiding the overhead of a full compiler toolchain.
  • COPY . .: transfers the application source without a virtual environment.
  • CMD: launches Gunicorn, the standard production entry point for Django.

According to the Docker documentation, multi‑stage builds reduce the attack surface because unnecessary binaries are omitted from the final image, and they improve layer‑caching efficiency.

Key point: A multi‑stage Dockerfile isolates build tools, producing a smaller, more secure image for a dockerized django rest framework with postgres deployment.


🗄️ Docker Compose — Orchestrating Services

Docker Compose defines the Django and PostgreSQL containers, handles networking, and propagates environment variables.

# docker-compose.yml
version: "3.9"
services: db: image: postgres:15-alpine environment: POSTGRES_USER: django_user POSTGRES_PASSWORD: secretpassword POSTGRES_DB: django_db volumes: - pgdata:/var/lib/postgresql/data healthcheck: test: ["CMD", "pg_isready", "-U", "django_user"] interval: 10s timeout: 5s retries: 5 web: build: . command: gunicorn myproject.wsgi:application -bind 0.0.0.0:8000 env_file: .env depends_on: db: condition: service_healthy ports: - "8000:8000" volumes: pgdata:
Enter fullscreen mode Exit fullscreen mode

What this does:

  • db service: runs the official PostgreSQL image with a pre‑configured user and database.
  • healthcheck: uses pg_isready to keep the Django container from starting until PostgreSQL reports readiness (exit code 0).
  • web service: builds the image from the Dockerfile and injects environment variables from .env.
  • depends_on: enforces a startup order based on the database’s health status.

📦 Service Definitions — Postgres

PostgreSQL runs in its own container, exposing port 5432 only to the Docker network; external access is blocked unless explicitly published.

🔧 Django Service — Application

The Django container reads its configuration from .env, keeping secrets out of image layers.

Key point: Docker Compose provides a reproducible, isolated environment for a dockerized django rest framework with postgres stack, handling service discovery and health checks automatically. (More onPythonTPoint tutorials)


⚙️ Django Settings — Connecting to Postgres

Reading database credentials from environment variables lets the container connect to the PostgreSQL service without hard‑coding secrets.

# myproject/settings.py
import os
from pathlib import Path BASE_DIR = Path(__file__).resolve().parent.parent SECRET_KEY = os.getenv('DJANGO_SECRET_KEY', 'fallback-secret') DEBUG = os.getenv('DJANGO_DEBUG', 'False') == 'True' ALLOWED_HOSTS = os.getenv('DJANGO_ALLOWED_HOSTS', 'localhost').split(',') DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', 'NAME': os.getenv('POSTGRES_DB', 'django_db'), 'USER': os.getenv('POSTGRES_USER', 'django_user'), 'PASSWORD': os.getenv('POSTGRES_PASSWORD', 'secretpassword'), 'HOST': os.getenv('POSTGRES_HOST', 'db'), 'PORT': os.getenv('POSTGRES_PORT', '5432'), }
}
Enter fullscreen mode Exit fullscreen mode

What this does:

  • os.getenv: pulls runtime values from the container’s environment, enabling the same image to be reused across environments.
  • ENGINE: selects the PostgreSQL backend.
  • HOST: uses the Docker Compose service name db, which Docker resolves to the correct container IP.

Because the settings are evaluated at import time, Django attempts a TCP connection to db:5432 as soon as the process starts. If the health check in docker-compose.yml succeeded, the connection succeeds.

Key point: Externalizing database credentials allows a dockerized django rest framework with postgres image to move from development to staging to production without code changes.


🔐 Secrets Management — Environment Variables

Storing secrets in a .env file that Docker Compose loads keeps them out of image layers and version control.

# .env
DJANGO_SECRET_KEY=super-secret-key
DJANGO_DEBUG=False
DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1
POSTGRES_DB=django_db
POSTGRES_USER=django_user
POSTGRES_PASSWORD=verystrongpassword
POSTGRES_HOST=db
POSTGRES_PORT=5432
Enter fullscreen mode Exit fullscreen mode

Docker Compose reads the file line‑by‑line and injects each entry as an environment variable into the web container. The file should have permissions 0600 and be excluded from source control.


🚀 Production Runtime — Process Management

Running Django with Gunicorn under supervisord provides automatic restarts and centralized log handling.

# supervisord.conf
[program:django]
command=gunicorn myproject.wsgi:application -bind 0.0.0.0:8000
directory=/app
autostart=true
autorestart=true
stdout_logfile=/var/log/gunicorn_stdout.log
stderr_logfile=/var/log/gunicorn_stderr.log
Enter fullscreen mode Exit fullscreen mode

What this does:

  • command: launches Gunicorn with the WSGI entry point.
  • autorestart: restarts the process if it exits unexpectedly, ensuring resilience.
  • stdout_logfile / stderr_logfile: capture request and error streams for later inspection.

To start the supervisor inside the container:

$ docker compose up -d
Creating network "myproject_default" with the default driver
Creating volume "myproject_pgdata" ...
Creating myproject_db_1 ... done
Creating myproject_web_1 ... done
Enter fullscreen mode Exit fullscreen mode

When the containers are running, query the health endpoint:

$ curl -s http://localhost:8000/health/
{"status":"ok"}
Enter fullscreen mode Exit fullscreen mode

The response confirms that Django successfully connected to PostgreSQL and that Gunicorn is serving requests.

Key point: A lightweight process manager guarantees that the dockerized django rest framework with postgres service recovers from crashes without manual intervention.


📊 Monitoring & Logging — Observability

Exporting metrics via Prometheus and shipping logs to a centralized system provides visibility into request latency, database connection pool usage, and container resource consumption.

# myproject/metrics.py
from prometheus_client import Counter, Histogram REQUEST_COUNT = Counter('django_http_requests_total', 'Total HTTP requests', ['method', 'endpoint'])
REQUEST_LATENCY = Histogram('django_http_request_latency_seconds', 'HTTP request latency', ['endpoint']) def record_request(method, endpoint, latency): REQUEST_COUNT.labels(method=method, endpoint=endpoint).inc() REQUEST_LATENCY.labels(endpoint=endpoint).observe(latency)
Enter fullscreen mode Exit fullscreen mode

What this does:

  • Counter: increments for each request, enabling rate calculations.
  • Histogram: records latency buckets, useful for SLA monitoring.
  • record_request: a helper that view functions can call to feed data to Prometheus.

Expose the metrics endpoint in urls.py and configure the container to listen on port 9100. A Prometheus scrape target can then be added to the monitoring stack.


🟩 Final Thoughts

The combination of a multi‑stage Dockerfile, Docker Compose service definitions, environment‑driven Django settings, and a robust process manager yields a production‑ready dockerized django rest framework with postgres deployment. Build tools are isolated, the attack surface is minimized, and Docker’s built‑in networking keeps the application and database tightly coupled without exposing credentials.

This pattern allows a single codebase to be built once and run anywhere Docker is available—from local workstations to cloud VMs—while preserving consistent behavior and observability. Adding HTTPS termination, advanced health checks, or rolling updates follows the same composable approach.


❓ Frequently Asked Questions

How do I run database migrations in this Docker setup?

Execute the migration command inside the running web container:

$ docker compose exec web python manage.py migrate
Operations to perform: Apply all migrations: admin, auth, contenttypes, sessions, myapp
Running migrations: Applying myapp.0001_initial... OK
Enter fullscreen mode Exit fullscreen mode




Can I replace PostgreSQL with another database without rebuilding the image?

Yes. Adjust the POSTGRES_* variables in .env and modify the DATABASES section in settings.py to point to the new service name. Because the application reads all values at runtime, no image rebuild is required.

What is the recommended way to handle static files in this container?

Collect static assets with python manage.py collectstatic during the image build, then serve them via a dedicated Nginx container or a CDN. Keeping static files out of the Django process reduces memory pressure and improves response times.


💡 Want to practise this hands-on? DigitalOcean gives new accounts $200 free credit for 60 days — enough to spin up a full Linux/Docker/Kubernetes environment at no cost.

📚 Recommended reading: Best DevOps & cloud books on Amazon — from Linux fundamentals to Kubernetes in production, curated for working engineers.

📚 References & Further Reading

  • Official Docker documentation on multi‑stage builds — guidance on reducing image size: docs.docker.com
  • Official Django documentation on database settings — details on configuring PostgreSQL backends: docs.djangoproject.com
  • PostgreSQL official documentation — connection parameters and security considerations: postgresql.org

Top comments (0)