FAST API en servidores de desarrollo

Ejecutar una API con fastapi dev, publicar Uvicorn directamente en Internet o depender de un único proceso puede provocar problemas de capacidad, TLS y recuperación ante fallos.

Para producción, FastAPI recomienda construir una imagen propia y ejecutar fastapi run, que utiliza Uvicorn. La terminación TLS puede delegarse a Nginx, un balanceador o el proveedor cloud. La estrategia de workers depende del entorno: varios procesos pueden resultar útiles en un servidor único, mientras que en Kubernetes suele convenir un proceso por contenedor y varias réplicas. Documentación de FastAPI sobre contenedores.

Cuándo conviene aplicarlo

Este esquema resulta útil para:

  • APIs y microservicios Python.
  • Backends para aplicaciones web o móviles.
  • Servicios internos con requisitos de disponibilidad.
  • Despliegues en Docker Compose, Kubernetes o servicios gestionados.

Requisitos previos

Necesitarás:

  • Un proyecto FastAPI funcional.
  • Dependencias bloqueadas en requirements.txt, uv.lock o un formato equivalente.
  • Docker y acceso al registry.
  • Variables de entorno definidas fuera de la imagen.
  • Nginx o un balanceador si necesitas TLS y proxy inverso.
  • Endpoints de salud y una estrategia de logs.
  • Límites de CPU y memoria medidos para la aplicación.

Arquitectura recomendada

El flujo habitual conecta:

  1. Cliente.
  2. Nginx o balanceador con terminación TLS.
  3. Red privada de contenedores.
  4. Contenedor FastAPI.
  5. Uno o varios workers.
  6. Logs, métricas y comprobaciones de salud.

Solo el proxy debería publicar un puerto hacia Internet. FastAPI puede escuchar en 0.0.0.0:8000 dentro de la red privada sin exponer ese puerto directamente en el host.

Paso 1: preparar el Dockerfile

El siguiente ejemplo utiliza Python 3.12, instala primero las dependencias para aprovechar la caché y ejecuta la aplicación con un usuario sin privilegios:

# syntax=docker/dockerfile:1
FROM python:3.12-slim

ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1

WORKDIR /app

RUN addgroup --system --gid 10001 app \
    && adduser --system \
        --uid 10001 \
        --ingroup app \
        --home /nonexistent \
        --no-create-home \
        app

COPY requirements.txt /app/requirements.txt

RUN pip install \
    --no-cache-dir \
    --upgrade \
    -r /app/requirements.txt

COPY --chown=10001:10001 ./app /app/app

USER 10001:10001

EXPOSE 8000

HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
    CMD ["python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health/live', timeout=2)"]

CMD ["fastapi", "run", "app/main.py", "--host", "0.0.0.0", "--port", "8000"]

La forma JSON de CMD permite que el proceso reciba correctamente las señales de terminación y ejecute los eventos de cierre de FastAPI. COPY --chown evita dejar el código bajo propiedad de root. Referencia de Dockerfile.

Agrega también un .dockerignore:

.git
.github
.env
.env.*
__pycache__/
*.py[cod]
.venv/
tests/
dist/

No excluyas migraciones, certificados públicos u otros archivos que la aplicación necesite en tiempo de ejecución.

Paso 2: implementar liveness y readiness

Liveness responde si el proceso ASGI está funcionando. No debe consultar servicios externos:

from fastapi import FastAPI, HTTPException

app = FastAPI()


@app.get("/health/live", include_in_schema=False)
async def liveness() -> dict[str, str]:
    return {"status": "ok"}

Readiness indica si la instancia puede recibir tráfico. Aquí sí puede realizar una comprobación ligera de dependencias:

@app.get("/health/ready", include_in_schema=False)
async def readiness() -> dict[str, str]:
    try:
        await check_database()
    except Exception as exc:
        raise HTTPException(
            status_code=503,
            detail="service not ready",
        ) from exc

    return {"status": "ready"}

check_database() representa una función específica de la aplicación. Debe utilizar un timeout corto y no ejecutar consultas costosas.

Evita devolver versiones, cadenas de conexión o detalles internos del error. Si estos endpoints no necesitan ser públicos, restríngelos a la red interna o al sistema de monitoreo.

Paso 3: construir y probar la imagen

Construye una imagen local:

docker build \
  --pull \
  --tag "fastapi-app:${GIT_SHA:-local}" \
  .

No es necesario escapar los dos puntos de la etiqueta.

Ejecuta el contenedor limitando recursos y publicando el puerto únicamente en la interfaz local:

docker run -d \
  --name fastapi-app \
  --restart unless-stopped \
  --stop-timeout 30 \
  --memory 1g \
  --cpus 2 \
  --env-file /ruta/segura/fastapi.env \
  --publish 127.0.0.1:8000:8000 \
  "fastapi-app:${GIT_SHA:-local}"

Los contenedores no tienen límites de CPU o memoria de forma predeterminada, por lo que deben definirse después de medir el consumo real. Límites de recursos en Docker.

Comprueba el estado:

curl --fail --silent --show-error \
  http://127.0.0.1:8000/health/live

docker inspect \
  --format '{{json .State.Health}}' \
  fastapi-app |
jq .

docker logs --tail 100 fastapi-app

Paso 4: elegir la estrategia de workers

En un servidor único o un despliegue sencillo con Docker Compose, puedes ejecutar varios workers:

fastapi run app/main.py \
  --host 0.0.0.0 \
  --port 8000 \
  --workers 4

Cada worker es un proceso separado y carga su propia copia de la aplicación. Cuatro workers no implican automáticamente cuatro veces más rendimiento, pero sí pueden acercarse a cuatro veces el consumo base de memoria.

Empieza con uno o dos workers y mide:

  • Latencia p50, p95 y p99.
  • Uso de CPU.
  • Memoria por proceso.
  • Errores y timeouts.
  • Tiempo de arranque.
  • Capacidad de la base de datos y del pool de conexiones.

En Kubernetes, Nomad o un sistema que ya replica contenedores, ejecuta normalmente un worker por contenedor y escala réplicas. FastAPI documenta explícitamente ambas estrategias. Workers y replicación.

Las migraciones de base de datos no deben ejecutarse una vez por worker. Ejecútalas como una tarea previa y controlada antes de habilitar las nuevas instancias.

Paso 5: configurar Nginx como proxy

Una configuración base puede utilizar un nombre DNS interno de Docker:

upstream fastapi_backend {
    server fastapi:8000;
    keepalive 32;
}

server {
    listen 443 ssl;
    server_name api.ejemplo.com;

    ssl_certificate     /etc/nginx/tls/fullchain.pem;
    ssl_certificate_key /etc/nginx/tls/private-key.pem;

    location / {
        proxy_pass http://fastapi_backend;
        proxy_http_version 1.1;

        proxy_set_header Host              $host;
        proxy_set_header X-Real-IP         $remote_addr;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        proxy_connect_timeout 5s;
        proxy_read_timeout 60s;
    }
}

Nginx permite modificar explícitamente Host y las cabeceras enviadas al servidor upstream mediante proxy_set_header. Documentación oficial de Nginx.

Valida antes de recargar:

nginx -t
nginx -s reload

Si utilizas WebSockets, agrega la configuración de Upgrade y Connection correspondiente. Si publicas la aplicación bajo un prefijo como /api, configura también root_path.

Paso 6: confiar en el proxy correcto

Para que FastAPI interprete HTTPS, host e IP original, habilita las cabeceras proxy y especifica las direcciones confiables:

fastapi run app/main.py \
  --host 0.0.0.0 \
  --port 8000 \
  --proxy-headers \
  --forwarded-allow-ips="172.20.0.0/24"

Sustituye el rango por la red real donde se encuentra Nginx.

También puede utilizarse --forwarded-allow-ips="*", pero únicamente cuando el puerto de FastAPI no admite conexiones directas y la red está suficientemente aislada. De otro modo, un cliente podría enviar cabeceras X-Forwarded-* falsas. FastAPI detrás de un proxy.

Paso 7: validar el despliegue completo

Realiza las comprobaciones en este orden:

curl --fail http://127.0.0.1:8000/health/live
curl --fail http://127.0.0.1:8000/health/ready
curl --fail https://api.ejemplo.com/health/ready

Después verifica:

  • Que una redirección genere una URL https://.
  • Que FastAPI no sea accesible desde Internet por el puerto 8000.
  • Que Nginx rechace certificados o configuraciones inválidas.
  • Que el contenedor pase a unhealthy cuando falle liveness.
  • Que readiness responda 503 cuando una dependencia no esté disponible.
  • Que un docker stop produzca un cierre ordenado.
  • Que los logs no contengan tokens, contraseñas ni cuerpos sensibles.
  • Que el servicio se recupere después de reiniciar un worker o contenedor.

Mantén la versión anterior de la imagen disponible hasta completar las pruebas de humo y confirmar las métricas.


Organigrama de despliegue de fast api

Problemas frecuentes

El contenedor reinicia continuamente

Comprueba los imports, variables obligatorias, permisos de archivos y el comando de inicio:

docker inspect fastapi-app
docker logs --tail 200 fastapi-app

Distingue entre un fallo de la aplicación, un healthcheck fallido y una terminación por falta de memoria.

Nginx devuelve 502

Verifica que FastAPI escuche en 0.0.0.0:8000, que ambos contenedores compartan red y que fastapi:8000 resuelva desde Nginx.

Las redirecciones utilizan HTTP

Confirma X-Forwarded-Proto, --proxy-headers y --forwarded-allow-ips. El problema suele aparecer cuando Uvicorn no confía en la dirección del proxy.

El contenedor queda sin memoria

Cada worker replica el estado de la aplicación. Reduce workers, revisa modelos o cachés cargados al inicio y establece un límite compatible con el consumo máximo observado.

El healthcheck pasa, pero la API no funciona

Probablemente solo se está verificando liveness. Agrega readiness con las dependencias imprescindibles y comprueba que el balanceador utilice el endpoint correcto.

Diagrama de implementacion de FAST API

Buenas prácticas para producción

  • Construye la imagen desde una versión fijada de Python.
  • Bloquea versiones y verifica dependencias en CI.
  • Ejecuta como usuario no root.
  • Mantén secretos fuera de la imagen.
  • Publica imágenes con tag de commit y conserva su digest.
  • Expón únicamente Nginx o el balanceador.
  • Separa liveness de readiness.
  • Envía logs estructurados a stdout y stderr.
  • Define timeouts y límites de recursos.
  • Mide latencia, errores, saturación y reinicios.
  • Prueba el cierre ordenado durante cada release.
  • Usa un worker por contenedor cuando el orquestador gestione réplicas.

Cloud Serversby Donweb

Todo el poder de la nube a tus proyectos y aplicaciones.
Alojamiento ultra rápido, escalable y con alta disponibilidad.

  • Performance que te sorprenderá
  • Rápida escalabilidad y sin limitaciones
  • Arquitectura de alta disponibilidad
  • Soporte experto y ejecutivos de cuenta
  • Pagos en tu moneda y facturación local

Descubre la mejor solución de Cloud Hosting