Docker y despliegue con Uvicorn/Gunicorn

Avanzado
FastAPI
FastAPI
Actualizado: 18/04/2026

Diagrama: Fastapi docker

Uvicorn en desarrollo vs producción

Durante el desarrollo usas:

uvicorn main:app --reload

El flag --reload hace que Uvicorn reinicie el servidor cuando detecta cambios. En producción no debes usar --reload (consume más recursos y no es seguro) y necesitas manejar múltiples workers para aprovechar todos los núcleos del servidor.

Uvicorn directamente en producción (servidor pequeño)

Para un servidor pequeño con pocos usuarios puedes usar Uvicorn directamente con múltiples workers:

uvicorn app.main:app --host 0.0.0.0 --port 8000 --workers 4

Gunicorn + Uvicorn (recomendado para producción)

Para producción sería, la combinación Gunicorn como gestor de procesos + Uvicorn como worker es la más robusta:

pip install gunicorn
gunicorn app.main:app \
    --workers 4 \
    --worker-class uvicorn.workers.UvicornWorker \
    --bind 0.0.0.0:8000 \
    --timeout 120 \
    --access-logfile - \
    --error-logfile -

La fórmula para el número de workers es: (2 × número_de_núcleos) + 1

Dockerfile para FastAPI

Un Dockerfile bien construido para FastAPI sigue el patrón multi-stage:

# Dockerfile

# --- Stage 1: Builder ---
# Instala las dependencias en un entorno separado
FROM python:3.12-slim as builder

WORKDIR /app

# Copiar requirements primero (aprovecha la caché de Docker)
COPY requirements.txt .

# Instalar dependencias en un directorio separado
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt

# --- Stage 2: Production ---
# Imagen final ligera sin herramientas de build
FROM python:3.12-slim

# Crear usuario no-root para seguridad
RUN useradd --create-home --shell /bin/bash appuser

WORKDIR /app

# Copiar dependencias instaladas desde el builder
COPY --from=builder /install /usr/local

# Copiar el código de la aplicación
COPY --chown=appuser:appuser . .

# Cambiar al usuario no-root
USER appuser

# Exponer el puerto
EXPOSE 8000

# Variables de entorno por defecto
ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1 \
    HOST=0.0.0.0 \
    PORT=8000

# Comando de producción
CMD ["gunicorn", "app.main:app", \
     "--worker-class", "uvicorn.workers.UvicornWorker", \
     "--workers", "4", \
     "--bind", "0.0.0.0:8000", \
     "--timeout", "120"]

Construcción y ejecución manual

# Construir la imagen
docker build -t mi-fastapi-app .

# Ejecutar el contenedor
docker run -d \
    --name mi-api \
    -p 8000:8000 \
    -e DATABASE_URL="postgresql://user:pass@db:5432/mibd" \
    -e SECRET_KEY="mi-clave-secreta" \
    mi-fastapi-app

requirements.txt para producción

# requirements.txt
fastapi[standard]>=0.115.0
uvicorn[standard]>=0.30.0
gunicorn>=22.0.0
sqlalchemy>=2.0.0
alembic>=1.13.0
pydantic-settings>=2.0.0
python-jose[cryptography]>=3.3.0
passlib[bcrypt]>=1.7.4
python-multipart>=0.0.9

docker-compose para desarrollo

Con docker-compose puedes levantar la API junto a la base de datos con un solo comando:

# docker-compose.yml
version: "3.9"

services:
  api:
    build: .
    ports:
      - "8000:8000"
    volumes:
      - .:/app   # Monta el código para recarga en caliente
    environment:
      DATABASE_URL: "postgresql://postgres:postgres@db:5432/mibd"
      SECRET_KEY: "dev-secret-key-cambiar-en-produccion"
      ENVIRONMENT: "desarrollo"
      DEBUG: "true"
    depends_on:
      db:
        condition: service_healthy
    command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: mibd
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 5

volumes:
  postgres_data:

Comandos útiles:

# Levantar todos los servicios
docker-compose up -d

# Ver los logs
docker-compose logs -f api

# Parar todos los servicios
docker-compose down

# Parar y eliminar volúmenes (reinicia la BD)
docker-compose down -v

.dockerignore

El archivo .dockerignore excluye archivos innecesarios de la imagen:

# .dockerignore
.git
.gitignore
.env
.env.*
__pycache__
*.pyc
*.pyo
.pytest_cache
htmlcov
.coverage
tests/
*.md
Dockerfile
docker-compose*.yml
.venv
venv

Migraciones automáticas con Alembic

Para ejecutar las migraciones de base de datos automáticamente al arrancar el contenedor, crea un script de entrada:

#!/bin/bash
# entrypoint.sh

echo "Ejecutando migraciones de base de datos..."
alembic upgrade head

echo "Iniciando el servidor..."
exec "$@"

Y en el Dockerfile:

# Añadir al final del Dockerfile
COPY --chown=appuser:appuser entrypoint.sh .
RUN chmod +x entrypoint.sh

ENTRYPOINT ["./entrypoint.sh"]
CMD ["gunicorn", "app.main:app", \
     "--worker-class", "uvicorn.workers.UvicornWorker", \
     "--workers", "4", \
     "--bind", "0.0.0.0:8000"]

Lifespan: eventos de arranque y apagado

FastAPI permite ejecutar código al arranque y apagado de la aplicación con la función lifespan:

# app/main.py
from contextlib import asynccontextmanager
from fastapi import FastAPI
import logging

logger = logging.getLogger(__name__)

@asynccontextmanager
async def lifespan(app: FastAPI):
    """Gestiona el ciclo de vida de la aplicación."""
    # Código de arranque
    logger.info("Iniciando la aplicación...")

    # Aquí puedes:
    # - Conectar a la base de datos
    # - Cargar modelos de ML
    # - Inicializar caché
    # - Verificar que los servicios externos están disponibles
    await conectar_servicios()

    logger.info("Aplicación lista para recibir peticiones")

    yield  # La aplicación está corriendo

    # Código de apagado
    logger.info("Apagando la aplicación...")
    await desconectar_servicios()
    logger.info("Aplicación apagada correctamente")

async def conectar_servicios():
    """Inicializa las conexiones a servicios externos."""
    # Ejemplo: pool de conexiones a base de datos
    pass

async def desconectar_servicios():
    """Cierra las conexiones a servicios externos."""
    pass

app = FastAPI(
    title="Mi API",
    lifespan=lifespan
)

El uso correcto del lifespan garantiza que los recursos se inicializan antes de recibir peticiones y se liberan correctamente cuando la aplicación se detiene, evitando memory leaks y conexiones colgadas en producción.

Variables de entorno en producción

En producción nunca uses archivos .env en el contenedor. Las variables de entorno deben inyectarse directamente:

  • Docker Swarm: secrets y configs
  • Kubernetes: ConfigMaps y Secrets
  • AWS ECS: Task Definitions con secretos de Secrets Manager
  • Google Cloud Run: variables de entorno en la definición del servicio
  • Heroku / Railway / Render: panel de configuración de la plataforma
# Ejemplo en Cloud Run (Google)
gcloud run deploy mi-api \
    --image gcr.io/mi-proyecto/mi-fastapi-app \
    --set-env-vars "ENVIRONMENT=produccion" \
    --set-secrets "DATABASE_URL=database-url:latest,SECRET_KEY=secret-key:latest"

El uso de secrets managers garantiza que las credenciales nunca se almacenan en texto plano en ningún lugar del pipeline de despliegue.

Fuentes y referencias

Documentación oficial y recursos externos para profundizar en FastAPI

Documentación oficial de FastAPI
Alan Sastre - Autor del tutorial

Alan Sastre

Ingeniero de Software y formador, CEO en CertiDevs

Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, FastAPI es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.

Más tutoriales de FastAPI

Explora más contenido relacionado con FastAPI y continúa aprendiendo con nuestros tutoriales gratuitos.

Aprendizajes de esta lección

Crear un Dockerfile optimizado para aplicaciones FastAPI con multi-stage build. Configurar Uvicorn y Gunicorn juntos para máximo rendimiento en producción. Usar docker-compose para desarrollo local con base de datos y otros servicios. Aplicar buenas prácticas de seguridad en imágenes Docker para FastAPI. Ejecutar migraciones de base de datos automáticamente al desplegar el contenedor.