Despliegue de Aplicaciones FastAPI en Linux

FastAPI es un framework web moderno de Python para construir APIs de alto rendimiento, con soporte nativo para tipos estáticos, documentación automática y programación asíncrona. Desplegar FastAPI en producción en Linux implica configurar Uvicorn como servidor ASGI, Gunicorn como gestor de procesos, Nginx como proxy inverso y systemd para gestión del servicio. Esta guía cubre el despliegue completo de FastAPI, incluyendo SSL, optimización de rendimiento y despliegue con Docker.

Requisitos Previos

  • Ubuntu 20.04+ o Rocky Linux 8+
  • Python 3.8+ instalado
  • Nginx instalado
  • Acceso root o sudo
  • Dominio configurado apuntando al servidor (para SSL)
# Verificar Python
python3 --version

# Instalar herramientas necesarias
apt update && apt install -y python3-pip python3-venv nginx certbot python3-certbot-nginx
# dnf install -y python3 python3-pip nginx certbot python3-certbot-nginx  # Rocky Linux

Preparación del Entorno Python

# Crear usuario dedicado para la aplicación
useradd -r -s /bin/bash -d /opt/fastapi-app fastapi
mkdir -p /opt/fastapi-app
chown fastapi:fastapi /opt/fastapi-app

# Crear entorno virtual Python
su - fastapi -s /bin/bash
python3 -m venv /opt/fastapi-app/venv
source /opt/fastapi-app/venv/bin/activate

# Instalar FastAPI y sus dependencias de producción
pip install fastapi uvicorn[standard] gunicorn

# Instalar dependencias del proyecto
# pip install -r requirements.txt

# Crear aplicación FastAPI de ejemplo
cat > /opt/fastapi-app/main.py << 'EOF'
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import Optional

app = FastAPI(
    title="Mi API",
    description="API de producción con FastAPI",
    version="1.0.0"
)

class Item(BaseModel):
    name: str
    price: float
    is_offer: Optional[bool] = None

@app.get("/")
async def root():
    return {"message": "API operativa", "status": "ok"}

@app.get("/health")
async def health_check():
    return {"status": "healthy"}

@app.get("/items/{item_id}")
async def read_item(item_id: int):
    if item_id > 1000:
        raise HTTPException(status_code=404, detail="Item no encontrado")
    return {"item_id": item_id, "name": "Item de ejemplo"}

@app.post("/items/")
async def create_item(item: Item):
    return {"item": item, "message": "Item creado"}
EOF

# Verificar que la app inicia correctamente
cd /opt/fastapi-app
venv/bin/uvicorn main:app --host 127.0.0.1 --port 8000 --reload &
sleep 2
curl http://127.0.0.1:8000/health
kill %1

Configuración de Uvicorn y Gunicorn

# Crear archivo de configuración de Gunicorn
cat > /opt/fastapi-app/gunicorn.conf.py << 'EOF'
# Configuración de Gunicorn para FastAPI en producción

import multiprocessing

# Dirección y puerto de enlace
bind = "unix:/opt/fastapi-app/fastapi.sock"
# Alternativa TCP: bind = "127.0.0.1:8000"

# Número de workers (2-4 por núcleo de CPU)
workers = multiprocessing.cpu_count() * 2 + 1

# Clase de worker ASGI para FastAPI
worker_class = "uvicorn.workers.UvicornWorker"

# Hilos por worker
threads = 2

# Timeout en segundos
timeout = 120
keepalive = 5

# Logs
accesslog = "/var/log/fastapi/access.log"
errorlog = "/var/log/fastapi/error.log"
loglevel = "warning"
access_log_format = '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s" %(D)s'

# Reiniciar workers periódicamente para evitar fugas de memoria
max_requests = 1000
max_requests_jitter = 50

# Directorio de trabajo
chdir = "/opt/fastapi-app"

# PID file
pidfile = "/opt/fastapi-app/gunicorn.pid"
EOF

# Crear directorio de logs
mkdir -p /var/log/fastapi
chown fastapi:fastapi /var/log/fastapi

# Probar la configuración de Gunicorn
su - fastapi -s /bin/bash -c "
    source /opt/fastapi-app/venv/bin/activate
    gunicorn -c /opt/fastapi-app/gunicorn.conf.py main:app
"

Servicio systemd para FastAPI

# Crear el servicio systemd
cat > /etc/systemd/system/fastapi.service << 'EOF'
[Unit]
Description=FastAPI Application con Gunicorn
After=network.target
Wants=network-online.target

[Service]
Type=notify
User=fastapi
Group=fastapi
WorkingDirectory=/opt/fastapi-app
RuntimeDirectory=fastapi

# Activar entorno virtual
Environment=PATH=/opt/fastapi-app/venv/bin:/usr/local/bin:/usr/bin:/bin
Environment=PYTHONDONTWRITEBYTECODE=1
Environment=PYTHONUNBUFFERED=1

# Cargar variables de entorno desde archivo .env
EnvironmentFile=-/opt/fastapi-app/.env

# Comando de inicio
ExecStart=/opt/fastapi-app/venv/bin/gunicorn \
    -c /opt/fastapi-app/gunicorn.conf.py \
    main:app

ExecReload=/bin/kill -s HUP $MAINPID

# Reinicio automático
Restart=on-failure
RestartSec=5s

# Seguridad
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ReadWritePaths=/opt/fastapi-app /var/log/fastapi

[Install]
WantedBy=multi-user.target
EOF

# Habilitar y arrancar el servicio
systemctl daemon-reload
systemctl enable --now fastapi.service
systemctl status fastapi.service

# Ver logs en tiempo real
journalctl -u fastapi.service -f

Nginx como Proxy Inverso

# Configurar Nginx para FastAPI
cat > /etc/nginx/sites-available/fastapi << 'EOF'
# Rate limiting para la API
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;

# Upstream FastAPI (socket Unix)
upstream fastapi_app {
    server unix:/opt/fastapi-app/fastapi.sock fail_timeout=0;
}

server {
    listen 80;
    server_name api.midominio.com;
    
    # Redirigir a HTTPS
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    server_name api.midominio.com;

    # Certificados SSL (ver sección siguiente)
    ssl_certificate /etc/letsencrypt/live/api.midominio.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.midominio.com/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;

    # Límite de tamaño del cuerpo de la solicitud
    client_max_body_size 100M;

    # Timeouts
    proxy_connect_timeout 60s;
    proxy_read_timeout 60s;
    proxy_send_timeout 60s;

    # Headers de seguridad
    add_header X-Content-Type-Options nosniff;
    add_header X-Frame-Options DENY;
    add_header X-XSS-Protection "1; mode=block";

    # Proxy a Gunicorn
    location / {
        # Rate limiting
        limit_req zone=api_limit burst=20 nodelay;

        proxy_pass http://fastapi_app;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        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_redirect off;
    }

    # Documentación OpenAPI (acceso restringido en producción)
    location /docs {
        # Permitir solo desde IPs internas
        allow 10.0.0.0/8;
        allow 192.168.0.0/16;
        deny all;
        
        proxy_pass http://fastapi_app;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }

    location /redoc {
        allow 10.0.0.0/8;
        deny all;
        proxy_pass http://fastapi_app;
    }

    # Endpoint de health check (sin rate limit)
    location /health {
        proxy_pass http://fastapi_app;
        proxy_set_header Host $host;
        access_log off;
    }
}
EOF

# Habilitar el sitio
ln -s /etc/nginx/sites-available/fastapi /etc/nginx/sites-enabled/
nginx -t
systemctl reload nginx

Configuración SSL con Let's Encrypt

# Obtener certificado SSL gratuito con Certbot
certbot --nginx -d api.midominio.com --email [email protected] --agree-tos --no-eff-email

# Verificar renovación automática
certbot renew --dry-run

# Verificar que el timer de renovación está activo
systemctl status certbot.timer

Despliegue con Docker

# Dockerfile para FastAPI
cat > /opt/fastapi-app/Dockerfile << 'EOF'
FROM python:3.11-slim

# Crear usuario no root
RUN useradd -r -s /bin/bash appuser

# Directorio de trabajo
WORKDIR /app

# Instalar dependencias del sistema
RUN apt-get update && apt-get install -y \
    gcc \
    && rm -rf /var/lib/apt/lists/*

# Instalar dependencias Python
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copiar código de la aplicación
COPY . .
RUN chown -R appuser:appuser /app

USER appuser

# Exponer puerto
EXPOSE 8000

# Comando de inicio
CMD ["gunicorn", "-c", "gunicorn.conf.py", "main:app"]
EOF

# Docker Compose para producción
cat > /opt/fastapi-app/docker-compose.yml << 'EOF'
version: '3.8'

services:
  fastapi:
    build: .
    container_name: fastapi-app
    restart: unless-stopped
    ports:
      - "8000:8000"
    volumes:
      - ./logs:/var/log/fastapi
    environment:
      - DATABASE_URL=postgresql://user:pass@db:5432/dbname
      - SECRET_KEY=${SECRET_KEY}
    env_file:
      - .env
    networks:
      - api-network
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 30s
      timeout: 10s
      retries: 3

  db:
    image: postgres:15-alpine
    restart: unless-stopped
    environment:
      POSTGRES_DB: apidb
      POSTGRES_USER: apiuser
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - postgres_data:/var/lib/postgresql/data
    networks:
      - api-network

volumes:
  postgres_data:

networks:
  api-network:
    driver: bridge
EOF

# Construir y desplegar
docker-compose up -d --build

# Verificar estado
docker-compose ps
docker-compose logs fastapi

Optimización de Rendimiento

# Ajustar el número de workers según la carga
# Para aplicaciones IO-bound (más conexiones concurrentes)
# workers = (2 * cpu_count) + 1

# Para aplicaciones CPU-bound (cálculos intensivos)
# workers = cpu_count

# Ver número de CPUs disponibles
nproc

# Configurar límites del sistema para muchas conexiones
cat >> /etc/security/limits.conf << 'EOF'
fastapi soft nofile 65536
fastapi hard nofile 65536
EOF

cat >> /etc/sysctl.d/99-fastapi.conf << 'EOF'
# Optimizaciones para servidor API de alto tráfico
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535
net.core.netdev_max_backlog = 5000
EOF
sysctl -p /etc/sysctl.d/99-fastapi.conf

# Configurar rotación de logs de Nginx y FastAPI
cat > /etc/logrotate.d/fastapi << 'EOF'
/var/log/fastapi/*.log {
    daily
    rotate 14
    compress
    delaycompress
    missingok
    notifempty
    sharedscripts
    postrotate
        systemctl reload fastapi
    endscript
}
EOF

Solución de Problemas

El servicio FastAPI no inicia:

journalctl -u fastapi.service -n 50
# Ver errores de Python específicos
su - fastapi -s /bin/bash -c "source /opt/fastapi-app/venv/bin/activate && python main.py"

Error 502 Bad Gateway en Nginx:

# Verificar que el socket existe
ls -la /opt/fastapi-app/fastapi.sock
# Verificar permisos del socket
stat /opt/fastapi-app/fastapi.sock
# Gunicorn debe poder escribir en el socket y Nginx leer
ls -la /opt/fastapi-app/
# El usuario nginx debe tener acceso al directorio del socket
usermod -aG fastapi www-data

Aplicación lenta en producción:

# Verificar número de workers activos
ps aux | grep gunicorn | wc -l
# Ver métricas de tiempo de respuesta en logs de Nginx
tail -100 /var/log/nginx/access.log | awk '{print $NF}' | sort -n | tail -20
# Ajustar timeout y workers en gunicorn.conf.py

Errores de importación de módulos Python:

# Verificar que el entorno virtual está activado
source /opt/fastapi-app/venv/bin/activate
python -c "import fastapi; print(fastapi.__version__)"
# Reinstalar dependencias si hay conflictos
pip install -r requirements.txt --force-reinstall

Conclusión

Desplegar FastAPI en producción en Linux con Gunicorn, systemd y Nginx como proxy inverso proporciona una arquitectura robusta, escalable y fácil de mantener. La combinación de workers asíncronos de Uvicorn con la gestión de procesos de Gunicorn permite manejar alta concurrencia eficientemente, mientras que systemd garantiza la disponibilidad continua del servicio. Para aplicaciones de mayor escala, considera añadir Redis para caché, Celery para tareas asíncronas y un balanceador de carga delante de múltiples instancias.