Saltar a contenido

13 — Dockerfile

Anterior: 12 — Cloud Tasks y Scheduler

Un Dockerfile es una receta para construir una imagen Docker. Este capítulo crea uno optimizado para Django en Cloud Run.


El Dockerfile

Crea Dockerfile en la raíz del repo (junto a manage.py):

# ── Base image ────────────────────────────────────────────────────────────────
FROM python:3.12-slim

# ── Install uv ───────────────────────────────────────────────────────────────
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /usr/local/bin/

# ── Working directory ─────────────────────────────────────────────────────────
WORKDIR /app

# ── Install Python dependencies ───────────────────────────────────────────────
COPY web/pyproject.toml web/uv.lock ./
RUN uv sync --frozen --no-dev

# ── Copy application source ───────────────────────────────────────────────────
COPY web/ .

# ── Environment variables ───────────────────────────────────────────────────────
ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1 \
    DJANGO_SETTINGS_MODULE=core.settings.prod \
    PORT=8080

# ── Port ──────────────────────────────────────────────────────────────────────
EXPOSE 8080

# ── Start command ─────────────────────────────────────────────────────────────
CMD ["uv", "run", "gunicorn", \
     "--bind", "0.0.0.0:8080", \
     "--workers", "2", \
     "--timeout", "60", \
     "--log-file", "-", \
     "core.wsgi"]

.dockerignore

Crea .dockerignore en la raíz del repo para prevenir que archivos innecesarios se envíen al contexto de build de Docker:

.git
web/.venv
web/media/
web/staticfiles/
web/htmlcov/
**/__pycache__
**/*.pyc
**/*.pyo
.env
*.md
DEPLOY/

Esto mantiene la imagen pequeña y previene que secretos se incluyan accidentalmente.


Opciones de Gunicorn explicadas

Opción Valor Por qué
--bind 0.0.0.0:8080 Escuchar en todas las interfaces, puerto 8080 Cloud Run espera el puerto 8080
--workers 2 2 procesos worker Manejar solicitudes concurrentes
--timeout 60 60 segundos de timeout Matar solicitudes lentas para prevenir bloqueo
--log-file - Escribir logs a stdout Cloud Logging captura stdout
core.wsgi Punto de entrada WSGI Located at web/core/wsgi.py

Construir y probar localmente

Construir la imagen:

docker build -t mycoolproject-app .

Probarla localmente (requiere un Postgres local o mock DATABASE_URL):

docker run --rm \
  -e DATABASE_URL="postgresql://postgres:postgres@host.docker.internal:5432/mycoolproject_dev" \
  -e SECRET_KEY="local-test-key" \
  -e ALLOWED_HOSTS="localhost" \
  -p 8080:8080 \
  mycoolproject-app

Visita http://localhost:8080 para verificar que inicia.


El nombre de la imagen

En main.tf, el servicio Cloud Run referencia la imagen:

image = "${google_artifact_registry_repository.app.repository_url}/app:latest"

Esto se resuelve a:

southamerica-east1-docker.pkg.dev/mycoolproject-prod/app-repo/app:latest

GitHub Actions hará push de imágenes con: - Etiqueta latest — siempre apunta al build más reciente - Etiqueta <git-sha> — única por commit, para rollbacks


Build de dos etapas (opcional)

Para imágenes más pequeñas, puedes usar un build de dos etapas:

# Stage 1: Build
FROM python:3.12-slim as builder
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /usr/local/bin/
WORKDIR /app
COPY web/pyproject.toml web/uv.lock ./
RUN uv sync --frozen --no-dev
COPY web/ .

# Stage 2: Runtime
FROM python:3.12-slim
WORKDIR /app
COPY --from=builder /app/web/ ./web/
COPY --from=builder /app/.venv/ ./.venv/
ENV PATH="/app/.venv/bin:$PATH"
ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1 \
    DJANGO_SETTINGS_MODULE=core.settings.prod \
    PORT=8080
EXPOSE 8080
CMD ["gunicorn", "--bind", "0.0.0.0:8080", "--workers", "2", "--timeout", "60", "--log-file", "-", "core.wsgi"]

Esto resulta en una imagen más pequeña porque excluye las herramientas de build. Por simplicidad, usamos la versión de una etapa en esta guía.