# Contenerizar una aplicación Django


## Requisitos previos

- Tienes instalada la última versión de [Docker
  Desktop](/get-started/get-docker/).
- Tienes [uv](https://docs.astral.sh/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](/get-started/docker-concepts/the-basics/what-is-a-container/)
> 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)](/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.

**Local (uv)**



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

   ```console
   $ uv init --python 3.14 django-docker
   $ cd django-docker
   ```

2. Agrega Django y Gunicorn, y luego estructura el proyecto Django:

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

**Contenedor (DHI)**



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:

   ```console
   $ mkdir django-docker && cd django-docker
   ```

2. Inicializa el proyecto, agrega las dependencias y estructúralo. Todo en una sola
   ejecución de contenedor:

   ```console
   $ 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:

```text
├── .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](/dhi/).

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

   ```console
   $ docker login dhi.io
   ```

2. Crea un archivo `.dockerignore` para excluir los artefactos locales del
   contexto de construcción:

   ```text {title=".dockerignore"}
   .venv/
   __pycache__/
   *.pyc
   .git/
   ```

3. Crea un `Dockerfile` con el siguiente contenido:

   ```dockerfile {title="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`:

   ```yaml {title="compose.yaml"}
   services:
     web:
       build: .
       ports:
         - "8000:8000"
   ```

### Ejecutar la aplicación

Desde el directorio `django-docker`, ejecuta:

```console
$ docker compose up --build
```

Abre un navegador y navega a [http://localhost:8000](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 {title="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:

```yaml {title="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](/compose/how-tos/file-watch/).

### Agregar el controlador de PostgreSQL

Agrega el adaptador `psycopg` a tu proyecto:

**Local (uv)**



```console
$ uv add 'psycopg[binary]'
```

**Contenedor (DHI)**



```console
$ 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:

```python {title="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:

```console
$ docker compose watch
```

Abre un navegador y navega a [http://localhost:8000](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:

- [Referencia de Dockerfile](/reference/dockerfile/)
- [Referencia del archivo Compose](/reference/compose-file/)
- [Usar Compose Watch](/compose/how-tos/file-watch/)
- [Imágenes Endurecidas de Docker](/dhi/)
- [Construcciones multi-etapa](/build/building/multi-stage/)
- [Documentación de uv](https://docs.astral.sh/uv/)

