Ir al contenido

Buscar en la documentación

Busca en la documentación de Skiffly

Flask y FastAPI

Despliega Flask, FastAPI, Starlette o cualquier app WSGI/ASGI en Skiffly — qué elige Railpack como comando de inicio, gunicorn y uvicorn en `PORT`, workers, bases de datos, tareas en segundo plano y health checks.

Para Railpack, ambos frameworks son proyectos Python normales; la página de Python y Django explica los gestores de dependencias, la versión de Python y los paquetes apt. Lo que cambia es el comando de inicio.

Comando de inicio#

Railpack deriva uno de las dependencias y del archivo de entrada:

DetectadoComando de inicio que usa Railpack
fastapi + uvicorn en las dependencias, main.pyuvicorn main:app --host 0.0.0.0 --port ${PORT:-8000}
flask + gunicorn en las dependencias, main.pygunicorn --bind 0.0.0.0:${PORT:-8000} main:app
en otro casopython main.py (luego app.py, start.py, bot.py, hello.py, server.py, el primero que exista)

Dos consecuencias: agrega el servidor a tus dependencias (uvicorn[standard] o gunicorn) y nombra el módulo y el objeto de la app como Railpack espera, o define tú mismo el comando de inicio en Configuración → Despliegue → Comando de inicio (startCommand, start: en configuración como código):

# FastAPI, módulo app/main.py, varios workers
uvicorn app.main:app --host 0.0.0.0 --port $PORT --workers 2 --proxy-headers --forwarded-allow-ips='*'
 
# Flask con gunicorn, función factory
gunicorn 'myapp:create_app()' --bind 0.0.0.0:$PORT --workers 2 --threads 4
 
# FastAPI bajo gunicorn
gunicorn app.main:app -k uvicorn.workers.UvicornWorker --bind 0.0.0.0:$PORT --workers 2

Si el comando de inicio es python main.py, lee el puerto en el código: uvicorn.run(app, host="0.0.0.0", port=int(os.environ.get("PORT", 8000))) / app.run(host="0.0.0.0", port=int(os.environ.get("PORT", 8000))). El servidor de desarrollo de Flask sirve para una demo, pero gunicorn es la opción para producción.

Puerto#

PORT es el puerto del servicio (8080 cuando el servicio no define uno). Enlaza a 0.0.0.0. --proxy-headers / ProxyFix hacen que request.url use https y el host público: el edge de Skiffly define X-Forwarded-Proto y X-Forwarded-For.

# Flask
from werkzeug.middleware.proxy_fix import ProxyFix
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1)

Bases de datos#

skiffly deploy --template postgres
skiffly variables set 'DATABASE_URL=${{Postgres.DATABASE_URL}}'

SQLAlchemy y SQLModel toman DATABASE_URL directamente (postgresql+psycopg://… si quieres el driver psycopg 3: reemplaza el esquema en el código, DATABASE_URL.replace("postgresql://", "postgresql+psycopg://", 1)). Para async, asyncpg con postgresql+asyncpg://. Migraciones con Alembic: sh -c "alembic upgrade head && uvicorn main:app --host 0.0.0.0 --port $PORT" como comando de inicio, o skiffly ssh -- alembic upgrade head como comando puntual; no hay fase de release. Flask-Migrate: flask db upgrade en el mismo lugar.

Redis para caché, límites de tasa o colas: skiffly deploy --template redis y luego REDIS_URL=${{Redis.REDIS_URL}}.

Trabajo en segundo plano#

  • Tareas cortas: BackgroundTasks de FastAPI corre dentro del proceso web; sirve para correos, no para trabajos de varios minutos.
  • Workers (Celery, RQ, arq, Dramatiq): un segundo servicio del mismo repositorio con el comando de inicio celery -A tasks worker, sin dominio, compartiendo REDIS_URL y DATABASE_URL mediante referencias.
  • Scripts programados: un servicio con programación cron y el script como comando de inicio (python jobs/nightly.py).

Health check#

@app.get("/healthz")
def healthz():
    return {"ok": True}

Configura la ruta del health check en /healthz. Mantenla libre de llamadas a la base de datos; el nodo la sondea cada pocos segundos y reinicia el contenedor tras tres fallos consecutivos.

Workers y memoria#

uvicorn --workers N o gunicorn --workers N bifurcan N procesos: con el límite por defecto de 2 vCPU / 2 GiB, 2 workers es un buen punto de partida para FastAPI (async maneja la concurrencia dentro de un worker); más para una app Flask bloqueante. Usa réplicas (Configuración → Despliegue → Réplicas) en lugar de decenas de workers en un solo contenedor.

Problemas comunes#

SíntomaSolución
[railpack] start command: python main.py y la app termina de inmediatoel archivo solo define app: agrega uvicorn a las dependencias o define un comando de inicio
FAILED después de 5 minutos, uvicorn registra Uvicorn running on http://127.0.0.1:8000--host 0.0.0.0 --port $PORT
ModuleNotFoundError: No module named 'app'la ruta del módulo en el comando de inicio no coincide con la estructura del repositorio (ajuste del directorio raíz, falta __init__.py)
redirect_uri o los enlaces generados usan http://activa las cabeceras de proxy (--proxy-headers, ProxyFix)
sqlalchemy.exc.OperationalError: could not connect al arrancarel servicio de base de datos todavía está arrancando: reintenta en el código o usa pool_pre_ping=True; revisa skiffly status
Las subidas desaparecen después de un desplieguedisco efímero: monta un volumen o usa almacenamiento de objetos

Ejemplo#

.skiffly/skiffly.ts
import { defineSkiffly, github, postgres, project, redis, service } from "@skiffly/config";
 
export default defineSkiffly(() => {
  const db = postgres("db");
  const cache = redis("cache");
  const api = service("api", {
    source: github("acme/api"),
    start: "sh -c 'alembic upgrade head && uvicorn app.main:app --host 0.0.0.0 --port $PORT --workers 2 --proxy-headers'",
    healthcheck: "/healthz",
    port: 8000,
    env: { DATABASE_URL: db.env.DATABASE_URL, REDIS_URL: cache.env.REDIS_URL },
    domain: true,
  });
  return project("acme-api", { resources: [db, cache, api] });
});

Landings: /deploy/fastapi · /deploy/flask