1
0
mirror of https://github.com/docker/docs.git synced 2026-10-25 06:09:46 +03:00
Files
docs/content/guides/django.md
David Karlsson f02e2a5633 Revamp the guides section: taxonomy, single-page structure, and new landing/filter UX (#25338)
## What

A full revamp of the `/guides` section — taxonomy, structure, and UX.

Preview: https://deploy-preview-25338--docsdocker.netlify.app/guides/

### Taxonomy & structure
- Replaces the sprawling 24-tag set with a focused 9-tag taxonomy
aligned to use case / SDLC: **Languages & frameworks, AI, Testing,
CI/CD, Security, Databases, Deployment, Admin, Hands-on labs**.
- Removes the languages taxonomy entirely (`data/languages.yaml` and all
`languages:` front matter).
- Collapses all multi-page guide series into single pages, with aliases
so old sub-page URLs continue to resolve.
- Deletes redirect-only shim pages and flattens directory-wrapped single
pages.

### Landing page (`/guides/`)
- A clean index: hero, then category sections listing each guide as a
plain **title / summary** row.
- **Browse rail** (sticky, left): click a topic to filter the page to
just that topic's guides; "All guides" resets. A count + Clear bar
appears while filtering.
- **Text filter**: instant client-side search over title, summary, and
tags. Topic filter and text search are mutually exclusive and share the
same flat-list view.
- No more "Featured guides" section.

### Guide content pages
- Removes the left sidebar for the guides section (mobile drawer kept).
- Centered reading column (`92ch`) with the TOC taking the remaining
width.
- Summary rendered as a lead paragraph under the h1.
- TOC scrollbar auto-hides until hover; breadcrumbs render at a
consistent size inside and outside `prose`.

## Notes
- Deep full-text search across guide bodies is still handled by the
existing site-wide Pagefind (⌘K); the landing filter is the fast
metadata-narrowing surface.
- `series.html` is retained — it's still used by `get-started` pages.

## Validation
- Site builds clean locally (Hugo dev server, no errors / broken refs).
- All collapsed sub-page URLs redirect via aliases.

## Follow-ups

- Prune guides that we no longer need/want
- Tighten the guides that span too many concepts (e.g. language guides;
remove cicd/deploy/sscs)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-01 10:47:10 +02:00

12 KiB

title, linkTitle, description, keywords, summary, params
title linkTitle description keywords summary params
Containerize a Django application Django Learn how to containerize a Django application using Docker. python, django, containerize, initialize, gunicorn, compose watch, uv This guide shows how to containerize a Django application using Docker. You'll scaffold the project with uv, create a production-ready Dockerfile using a Docker Hardened Image, then add a development stage and Compose Watch for fast iteration.
tags time
security
25 minutes

Prerequisites

  • You have installed the latest version of Docker Desktop.
  • You have uv installed, or you can use Docker to scaffold the project without a local Python or uv installation.

Tip

If you're new to Docker, start with the Docker basics guide to get familiar with key concepts like images, containers, and Dockerfiles.


Overview

This guide walks you through containerizing a Django application with Docker. By the end, you will:

  • Initialize a Django project using uv, either locally or inside a Docker Hardened Image container.
  • Create a production-ready Dockerfile using Docker Hardened Images (DHI).
  • Add a development stage to your Dockerfile and configure Compose Watch for automatic code syncing.

Create the Django project

You can bootstrap the project with a local uv installation, or entirely inside a container using the same DHI image the Dockerfile uses, with no local Python required.

{{< tabs >}} {{< tab name="Local (uv)" >}}

  1. Initialize the project pinned to Python 3.14, then navigate into it:

    $ uv init --python 3.14 django-docker
    $ cd django-docker
    
  2. Add Django and Gunicorn, then scaffold the Django project:

    $ uv add django gunicorn
    $ uv run django-admin startproject myapp .
    

{{< /tab >}} {{< tab name="Container (DHI)" >}}

The DHI dev image already has Python 3.14, so the bootstrapped project will match the Dockerfile exactly.

  1. Create the project directory and navigate into it:

    $ mkdir django-docker && cd django-docker
    
  2. Initialize the project, add dependencies, and scaffold. All in one container run:

    $ docker run --rm -v $PWD:$PWD -w $PWD \
      -e UV_LINK_MODE=copy \
      dhi.io/python:3.14-alpine3.23-dev \
      sh -c "pip install --quiet --root-user-action=ignore uv && uv init --name django-docker --python 3.14 . && uv add django gunicorn && uv run django-admin startproject myapp ."
    

    Note

    The previous command uses Mac/Linux shell syntax. On Windows, adjust the path: PowerShell uses ${PWD}, Command Prompt uses %cd%, Git Bash requires MSYS_NO_PATHCONV=1 with $(pwd -W).

{{< /tab >}} {{< /tabs >}}

Your directory should now contain the following files:

├── .python-version
├── main.py
├── manage.py
├── myapp/
│ ├── __init__.py
│ ├── asgi.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── pyproject.toml
├── uv.lock
└── README.md

Create a production Dockerfile

Docker Hardened Images are production-ready base images maintained by Docker that minimize attack surface. For more details, see Docker Hardened Images.

  1. Sign in to the DHI registry:

    $ docker login dhi.io
    
  2. Create a .dockerignore file to exclude local artifacts from the build context:

    .venv/
    __pycache__/
    *.pyc
    .git/
    
  3. Create a Dockerfile with the following contents:

    # syntax=docker/dockerfile:1
    
    # Build stage: the -dev image includes tools needed to install packages.
    FROM dhi.io/python:3.14-alpine3.23-dev AS builder
    
    # Prevent Python from writing .pyc files to disk.
    ENV PYTHONDONTWRITEBYTECODE=1
    # Prevent Python from buffering stdout/stderr so logs appear immediately.
    ENV PYTHONUNBUFFERED=1
    
    RUN pip install --quiet --root-user-action=ignore uv
    # Use copy mode since the cache and build filesystem are on different volumes.
    ENV UV_LINK_MODE=copy
    
    WORKDIR /app
    
    # Install dependencies into a virtual environment using cache and bind mounts
    # so neither uv nor the lock files need to be copied into the image.
    RUN --mount=type=cache,target=/root/.cache/uv \
        --mount=type=bind,source=uv.lock,target=uv.lock \
        --mount=type=bind,source=pyproject.toml,target=pyproject.toml \
        uv sync --frozen --no-install-project
    
    # Runtime stage: minimal DHI image with no shell or package manager,
    # already runs as the nonroot user.
    FROM dhi.io/python:3.14-alpine3.23
    
    # Prevent Python from buffering stdout/stderr so logs appear immediately.
    ENV PYTHONUNBUFFERED=1
    # Activate the virtual environment copied from the build stage.
    ENV PATH="/app/.venv/bin:$PATH"
    
    WORKDIR /app
    
    # Copy the pre-built virtual environment and application source code.
    COPY --from=builder /app/.venv /app/.venv
    COPY . .
    
    EXPOSE 8000
    
    # Run Gunicorn as the production WSGI server.
    CMD ["gunicorn", "myapp.wsgi:application", "--bind", "0.0.0.0:8000"]
    
  4. Create a compose.yaml file:

    services:
      web:
        build: .
        ports:
          - "8000:8000"
    

Run the application

From the django-docker directory, run:

$ docker compose up --build

Open a browser and navigate to http://localhost:8000. You should see the Django welcome page.

Press ctrl+c to stop the application.


Set up a development environment

The production setup uses Gunicorn and requires a full image rebuild to pick up code changes. For development, you can add a development stage to your Dockerfile that uses Django's built-in server, and configure Compose Watch to automatically sync code changes into the running container without a rebuild.

Update the Dockerfile

Replace your Dockerfile with a multi-stage version that adds a development stage alongside production:

# syntax=docker/dockerfile:1

# Build stage: the -dev image includes tools needed to install packages.
FROM dhi.io/python:3.14-alpine3.23-dev AS builder

# Prevent Python from writing .pyc files to disk.
ENV PYTHONDONTWRITEBYTECODE=1
# Prevent Python from buffering stdout/stderr so logs appear immediately.
ENV PYTHONUNBUFFERED=1

RUN pip install --quiet --root-user-action=ignore uv
# Use copy mode since the cache and build filesystem are on different volumes.
ENV UV_LINK_MODE=copy

WORKDIR /app

# Install dependencies into a virtual environment using cache and bind mounts
# so neither uv nor the lock files need to be copied into the image.
RUN --mount=type=cache,target=/root/.cache/uv \
    --mount=type=bind,source=uv.lock,target=uv.lock \
    --mount=type=bind,source=pyproject.toml,target=pyproject.toml \
    uv sync --frozen --no-install-project

# The development stage inherits the -dev image and virtual environment from
# the builder. Django's built-in server reloads when Compose Watch syncs files.
FROM builder AS development

ENV PATH="/app/.venv/bin:$PATH"

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

# The production stage uses the minimal runtime image, which has no shell,
# no package manager, and already runs as the nonroot user.
FROM dhi.io/python:3.14-alpine3.23 AS production

# Prevent Python from buffering stdout/stderr so logs appear immediately.
ENV PYTHONUNBUFFERED=1
# Activate the virtual environment copied from the build stage.
ENV PATH="/app/.venv/bin:$PATH"

WORKDIR /app

# Copy only the pre-built virtual environment and application source code.
COPY --from=builder /app/.venv /app/.venv
COPY . .

EXPOSE 8000

# Run Gunicorn as the production WSGI server.
CMD ["gunicorn", "myapp.wsgi:application", "--bind", "0.0.0.0:8000"]

Update the Compose file

Replace your compose.yaml with the following. It targets the development stage, adds a PostgreSQL database, and configures Compose Watch:

services:
  web:
    build:
      context: .
      # Build the development stage from the multi-stage Dockerfile.
      target: development
    ports:
      - "8000:8000"
    environment:
      # Enable Django's verbose debug error pages (the dev server always auto-reloads).
      - DEBUG=1
      # Database connection settings passed to Django via environment variables.
      - POSTGRES_DB=myapp
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=password
      - POSTGRES_HOST=db
      - POSTGRES_PORT=5432
    # Wait for the database to pass its healthcheck before starting the web service.
    depends_on:
      db:
        condition: service_healthy
    develop:
      watch:
        # Sync source file changes directly into the container so Django's
        # dev server can reload them without a full image rebuild.
        - action: sync
          path: .
          target: /app
          ignore:
            - __pycache__/
            - "*.pyc"
            - .git/
            - .venv/
        # Rebuild the image when dependencies change.
        - action: rebuild
          path: pyproject.toml
        - action: rebuild
          path: uv.lock
  db:
    image: dhi.io/postgres:18
    restart: always
    volumes:
      # Persist database data across container restarts.
      - db-data:/var/lib/postgresql
    environment:
      - POSTGRES_DB=myapp
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=password
    # Expose the port only to other services on the Compose network,
    # not to the host machine.
    expose:
      - 5432
    # Only report healthy once PostgreSQL is ready to accept connections,
    # so the web service doesn't start before the database is available.
    healthcheck:
      test: ["CMD", "pg_isready"]
      interval: 10s
      timeout: 5s
      retries: 5
volumes:
  db-data:

The sync action pushes file changes directly into the running container so Django's dev server reloads them automatically. A change to pyproject.toml or uv.lock triggers a full image rebuild instead.

Note

To learn more about Compose Watch, see Use Compose Watch.

Add the PostgreSQL driver

Add the psycopg adapter to your project:

{{< tabs >}} {{< tab name="Local (uv)" >}}

$ uv add 'psycopg[binary]'

{{< /tab >}} {{< tab name="Container (DHI)" >}}

$ docker run --rm -v $PWD:$PWD -w $PWD \
  -e UV_LINK_MODE=copy \
  dhi.io/python:3.14-alpine3.23-dev \
  sh -c "pip install --quiet --root-user-action=ignore uv && uv add 'psycopg[binary]'"

{{< /tab >}} {{< /tabs >}}

Then update myapp/settings.py to read DEBUG and DATABASES from environment variables:

import os

DEBUG = os.environ.get('DEBUG', '0') == '1'

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.postgresql",
        "NAME": os.environ.get("POSTGRES_DB", "myapp"),
        "USER": os.environ.get("POSTGRES_USER", "postgres"),
        "PASSWORD": os.environ.get("POSTGRES_PASSWORD", "password"),
        "HOST": os.environ.get("POSTGRES_HOST", "localhost"),
        "PORT": os.environ.get("POSTGRES_PORT", "5432"),
    }
}

Run with Compose Watch

Start the development stack:

$ docker compose watch

Open a browser and navigate to http://localhost:8000.

Try editing a file, for example add a view to myapp/views.py. Compose Watch syncs the change into the container and Django's dev server reloads automatically. If you update pyproject.toml or uv.lock, Compose Watch triggers a full image rebuild.

Press ctrl+c to stop.


Summary

In this guide, you:

  • Bootstrapped a Django project using uv, with options for both local and containerized setup.
  • Created a production-ready Dockerfile using Docker Hardened Images and uv for dependency management.
  • Added a development stage to the Dockerfile and configured Compose Watch for fast iterative development with a PostgreSQL database.

Related information: