Compartir comentarios
Las respuestas se generan en base a la documentación.

Contenerizar una aplicación Django

Requisitos previos

  • Tienes instalada la última versión de Docker Desktop.
  • Tienes uv instalado, o puedes usar Docker para estructurar el proyecto sin necesidad de una instalación local de Python o uv.
Tip

Si eres nuevo en Docker, comienza con la guía de conceptos básicos de Docker para familiarizarte con conceptos clave como imágenes, contenedores y Dockerfiles.


Resumen

Esta guía te guiará a través de la contenerización de una aplicación Django con Docker. Al finalizar, habrás logrado:

  • Inicializar un proyecto Django usando uv, ya sea localmente o dentro de un contenedor de Imagen Endurecida de Docker.
  • Crear un Dockerfile listo para producción usando Imágenes Endurecidas de Docker (DHI).
  • Agregar una etapa de development a tu Dockerfile y configurar Compose Watch para la sincronización automática de código.

Crear el proyecto Django

Puedes inicializar el proyecto con una instalación local de uv, o completamente dentro de un contenedor usando la misma imagen DHI que utiliza el Dockerfile, sin necesidad de tener Python local.

  1. Inicializa el proyecto fijado a Python 3.14 y luego navega dentro de él:

    $ uv init --python 3.14 django-docker
    $ cd django-docker
    
  2. Agrega Django y Gunicorn, y luego estructura el proyecto Django:

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

La imagen de desarrollo de DHI ya tiene Python 3.14, por lo que el proyecto inicializado coincidirá exactamente con el Dockerfile.

  1. Crea el directorio del proyecto y navega dentro de él:

    $ mkdir django-docker && cd django-docker
    
  2. Inicializa el proyecto, agrega las dependencias y estructúralo. Todo en una sola ejecución de contenedor:

    $ 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

    El comando anterior utiliza la sintaxis de shell de Mac/Linux. En Windows, ajusta la ruta: PowerShell utiliza ${PWD}, el Símbolo del sistema utiliza %cd%, Git Bash requiere MSYS_NO_PATHCONV=1 con $(pwd -W).

Tu directorio ahora debería contener los siguientes archivos:

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

Crear un Dockerfile de producción

Las Imágenes Endurecidas de Docker (Docker Hardened Images) son imágenes base listas para producción mantenidas por Docker que minimizan la superficie de ataque. Para más detalles, consulta Imágenes Endurecidas de Docker.

  1. Inicia sesión en el registro de DHI:

    $ docker login dhi.io
    
  2. Crea un archivo .dockerignore para excluir los artefactos locales del contexto de construcción:

    .dockerignore
    .venv/
    __pycache__/
    *.pyc
    .git/
  3. Crea un Dockerfile con el siguiente contenido:

    Dockerfile
    # syntax=docker/dockerfile:1
    
    # Etapa de construcción: la imagen -dev incluye las herramientas necesarias para instalar paquetes.
    FROM dhi.io/python:3.14-alpine3.23-dev AS builder
    
    # Evita que Python escriba archivos .pyc en el disco.
    ENV PYTHONDONTWRITEBYTECODE=1
    # Evita que Python almacene en búfer stdout/stderr para que los logs aparezcan inmediatamente.
    ENV PYTHONUNBUFFERED=1
    
    RUN pip install --quiet --root-user-action=ignore uv
    # Usa el modo copia ya que la caché y el sistema de archivos de construcción están en volúmenes diferentes.
    ENV UV_LINK_MODE=copy
    
    WORKDIR /app
    
    # Instala las dependencias en un entorno virtual usando montajes de caché y de tipo bind
    # de modo que ni uv ni los archivos de bloqueo necesiten copiarse en la imagen.
    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
    
    # Etapa de ejecución: imagen DHI mínima sin shell ni gestor de paquetes,
    # ya se ejecuta como el usuario no raíz (nonroot).
    FROM dhi.io/python:3.14-alpine3.23
    
    # Evita que Python almacene en búfer stdout/stderr para que los logs aparezcan inmediatamente.
    ENV PYTHONUNBUFFERED=1
    # Activa el entorno virtual copiado de la etapa de construcción.
    ENV PATH="/app/.venv/bin:$PATH"
    
    WORKDIR /app
    
    # Copia el entorno virtual preconstruido y el código fuente de la aplicación.
    COPY --from=builder /app/.venv /app/.venv
    COPY . .
    
    EXPOSE 8000
    
    # Ejecuta Gunicorn como el servidor WSGI de producción.
    CMD ["gunicorn", "myapp.wsgi:application", "--bind", "0.0.0.0:8000"]
  4. Crea un archivo compose.yaml:

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

Ejecutar la aplicación

Desde el directorio django-docker, ejecuta:

$ docker compose up --build

Abre un navegador y navega a http://localhost:8000. Deberías ver la página de bienvenida de Django.

Presiona ctrl+c para detener la aplicación.


Configurar un entorno de desarrollo

La configuración de producción utiliza Gunicorn y requiere una reconstrucción completa de la imagen para aplicar los cambios de código. Para el desarrollo, puedes agregar una etapa de development a tu Dockerfile que use el servidor integrado de Django, y configurar Compose Watch para sincronizar automáticamente los cambios de código en el contenedor en ejecución sin necesidad de reconstruir.

Actualizar el Dockerfile

Reemplaza tu Dockerfile con una versión multi-etapa que agregue una etapa de development junto con la de production:

Dockerfile
# syntax=docker/dockerfile:1

# Etapa de construcción: la imagen -dev incluye las herramientas necesarias para instalar paquetes.
FROM dhi.io/python:3.14-alpine3.23-dev AS builder

# Evita que Python escriba archivos .pyc en el disco.
ENV PYTHONDONTWRITEBYTECODE=1
# Evita que Python almacene en búfer stdout/stderr para que los logs aparezcan inmediatamente.
ENV PYTHONUNBUFFERED=1

RUN pip install --quiet --root-user-action=ignore uv
# Usa el modo copia ya que la caché y el sistema de archivos de construcción están en volúmenes diferentes.
ENV UV_LINK_MODE=copy

WORKDIR /app

# Instala las dependencias en un entorno virtual usando montajes de caché y de tipo bind
# de modo que ni uv ni los archivos de bloqueo necesiten copiarse en la imagen.
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

# La etapa de desarrollo hereda la imagen -dev y el entorno virtual del constructor.
# El servidor integrado de Django se reinicia cuando Compose Watch sincroniza los archivos.
FROM builder AS development

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

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

# La etapa de producción utiliza la imagen de ejecución mínima, que no tiene shell,
# ni gestor de paquetes, y ya se ejecuta como el usuario no raíz (nonroot).
FROM dhi.io/python:3.14-alpine3.23 AS production

# Evita que Python almacene en búfer stdout/stderr para que los logs aparezcan inmediatamente.
ENV PYTHONUNBUFFERED=1
# Activa el entorno virtual copiado de la etapa de construcción.
ENV PATH="/app/.venv/bin:$PATH"

WORKDIR /app

# Copia solo el entorno virtual preconstruido y el código fuente de la aplicación.
COPY --from=builder /app/.venv /app/.venv
COPY . .

EXPOSE 8000

# Ejecuta Gunicorn como el servidor WSGI de producción.
CMD ["gunicorn", "myapp.wsgi:application", "--bind", "0.0.0.0:8000"]

Actualizar el archivo Compose

Reemplaza tu compose.yaml con lo siguiente. Se dirige a la etapa de development, agrega una base de datos PostgreSQL y configura Compose Watch:

compose.yaml
services:
  web:
    build:
      context: .
      # Construye la etapa de desarrollo a partir del Dockerfile multi-etapa.
      target: development
    ports:
      - "8000:8000"
    environment:
      # Habilita las páginas de error de depuración detalladas de Django (el servidor de desarrollo siempre se reinicia automáticamente).
      - DEBUG=1
      # Configuración de conexión de base de datos pasada a Django a través de variables de entorno.
      - POSTGRES_DB=myapp
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=password
      - POSTGRES_HOST=db
      - POSTGRES_PORT=5432
    # Espera a que la base de datos pase su control de estado antes de iniciar el servicio web.
    depends_on:
      db:
        condition: service_healthy
    develop:
      watch:
        # Sincroniza los cambios en los archivos fuente directamente en el contenedor para que
        # el servidor de desarrollo de Django pueda recargarlos sin una reconstrucción completa de la imagen.
        - action: sync
          path: .
          target: /app
          ignore:
            - __pycache__/
            - "*.pyc"
            - .git/
            - .venv/
        # Reconstruye la imagen cuando cambian las dependencias.
        - action: rebuild
          path: pyproject.toml
        - action: rebuild
          path: uv.lock
  db:
    image: dhi.io/postgres:18
    restart: always
    volumes:
      # Persiste los datos de la base de datos a través de los reinicios del contenedor.
      - db-data:/var/lib/postgresql
    environment:
      - POSTGRES_DB=myapp
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=password
    # Expone el puerto solo a otros servicios en la red de Compose, no a la máquina host.
    expose:
      - 5432
    # Solo informa que está saludable una vez que PostgreSQL esté listo para aceptar conexiones,
    # para que el servicio web no se inicie antes de que la base de datos esté disponible.
    healthcheck:
      test: ["CMD", "pg_isready"]
      interval: 10s
      timeout: 5s
      retries: 5
volumes:
  db-data:

La acción sync empuja los cambios de archivos directamente en el contenedor en ejecución de modo que el servidor de desarrollo de Django se reinicie automáticamente. Un cambio en pyproject.toml o uv.lock activa una reconstrucción completa de la imagen en su lugar.

Note

Para obtener más información sobre Compose Watch, consulta Usar Compose Watch.

Agregar el controlador de PostgreSQL

Agrega el adaptador psycopg a tu proyecto:

$ uv add 'psycopg[binary]'
$ 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]'"

Luego, actualiza myapp/settings.py para leer DEBUG y DATABASES desde las variables de entorno:

myapp/settings.py
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"),
    }
}

Ejecutar con Compose Watch

Inicia el entorno de desarrollo:

$ docker compose watch

Abre un navegador y navega a http://localhost:8000.

Intenta editar un archivo, por ejemplo, agrega una vista a myapp/views.py. Compose Watch sincroniza el cambio en el contenedor y el servidor de desarrollo de Django se reinicia automáticamente. Si actualizas pyproject.toml o uv.lock, Compose Watch activa una reconstrucción completa de la imagen.

Presiona ctrl+c para detener.


Resumen

En esta guía, lograste:

  • Inicializar un proyecto Django usando uv, con opciones para configuración tanto local como contenerizada.
  • Crear un Dockerfile listo para producción usando Imágenes Endurecidas de Docker y uv para la gestión de dependencias.
  • Agregar una etapa de development al Dockerfile y configurar Compose Watch para un desarrollo iterativo rápido con una base de datos PostgreSQL.

Información relacionada: