Ir al contenido

Buscar en la documentación

Busca en la documentación de Skiffly

Python y Django

Despliega apps Python y proyectos Django en Skiffly con Railpack — pip, uv, Poetry, PDM y Pipenv, la versión de Python, gunicorn y `PORT`, Postgres y Redis, migraciones y `collectstatic`, archivos estáticos y errores comunes.

El proveedor de Python de Railpack construye cualquier proyecto Python; Django recibe algunos extras. Flask y FastAPI se cubren en su propia página.

Qué detecta Railpack#

RevisaEfecto
requirements.txt, pyproject.toml o Pipfile; o uno de main.py, app.py, start.py, bot.py, hello.py, server.pyel servicio es una app de Python
requirements.txt → pip · pyproject.toml + poetry.lock → Poetry · + pdm.lock → PDM · + uv.lock → uv · Pipfile → Pipenvcómo se instalan las dependencias
RAILPACK_PYTHON_VERSION, luego .python-version / .tool-versions / mise.toml, luego runtime.txt, luego Pipfilela versión de Python (por defecto 3.13)
manage.py + django en las dependenciasDjango: comando de inicio python manage.py migrate && gunicorn <project>.wsgi:application
psycopg/psycopg2, mysqlclient, pycairo, pdf2image, pydub en las dependenciasse instalan los paquetes apt correspondientes (libpq5, default-mysql-client, ffmpeg, …)

Railpack define PYTHONUNBUFFERED=1 (los logs aparecen de inmediato), PYTHONDONTWRITEBYTECODE=1 y PIP_DISABLE_PIP_VERSION_CHECK=1. Fija la versión con .python-version (3.12); el valor por defecto cambia con las versiones de Railpack.

Puerto y servidor#

Skiffly define PORT; el proceso debe enlazarse a 0.0.0.0:$PORT. El comando de inicio de Django que genera Railpack confía en que gunicorn lea PORT (gunicorn se enlaza a 0.0.0.0:$PORT cuando la variable está definida). De todos modos, hazlo explícito en tu propio comando de inicio:

gunicorn mysite.wsgi --bind 0.0.0.0:$PORT --workers 2 --timeout 60

Agrega gunicorn (o uvicorn + gunicorn con UvicornWorker para ASGI/Channels) a tus dependencias; Railpack no agrega servidores. python manage.py runserver es solo para desarrollo.

Comando de inicio de Django#

La plantilla de Django que incluye Skiffly usa este comando de inicio, que es un buen valor por defecto para la mayoría de los proyectos:

python manage.py migrate && python manage.py collectstatic --noinput && gunicorn mysite.wsgi --bind 0.0.0.0:$PORT

Configúralo en Configuración → Despliegue → Comando de inicio (o startCommand / start: en configuración como código) y reemplaza mysite por el paquete de tu proyecto. RAILPACK_DJANGO_APP_NAME=mysite.wsgi hace lo mismo con el comando generado por Railpack si prefieres conservarlo.

Ajustes que importan en producción:

import os
DEBUG = os.environ.get("DEBUG", "") == "1"
SECRET_KEY = os.environ["SECRET_KEY"]
ALLOWED_HOSTS = [os.environ.get("SKIFFLY_PUBLIC_DOMAIN", "localhost"), "app.example.com"]
CSRF_TRUSTED_ORIGINS = [f"https://{h}" for h in ALLOWED_HOSTS]
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
DATABASES = {"default": dj_database_url.config(conn_max_age=60)}   # lee DATABASE_URL

SKIFFLY_PUBLIC_DOMAIN se define automáticamente en cuanto el servicio tiene un dominio. Genera SECRET_KEY una sola vez (skiffly variables set SECRET_KEY=$(openssl rand -hex 32) o generate("hex64") en configuración como código).

Postgres y Redis#

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

DATABASE_URL es postgresql://postgres:…@postgres:5432/app en la red privada; tanto dj-database-url como DATABASES["default"] = {"ENGINE": "django.db.backends.postgresql", …} a partir de urllib.parse funcionan. Instala psycopg[binary] (o psycopg2-binary); Railpack agrega libpq cuando ve el paquete. Celery y django-redis toman REDIS_URL tal cual. SQLite también funciona, pero solo con un volumen montado donde vive el archivo, y con una sola réplica.

Migraciones#

No hay fase de release. Mantén python manage.py migrate en el comando de inicio (se ejecuta antes de gunicorn en cada despliegue; el arranque tiene hasta cinco minutos antes de que el health check se rinda) o ejecútalo como comando puntual desde tu máquina cuando quieras controlar el momento:

skiffly ssh -- python manage.py migrate
skiffly ssh -- python manage.py createsuperuser

Con varias réplicas, cada contenedor ejecuta el comando de inicio; la tabla de migraciones de Django hace que las llamadas concurrentes a migrate sean seguras para migraciones normales, pero las migraciones de datos largas van en un comando puntual.

Archivos estáticos y de medios#

  • Estáticos: collectstatic en el comando de inicio (arriba) o en el comando de build, y luego sírvelos con WhiteNoise (whitenoise.middleware.WhiteNoiseMiddleware, STATIC_ROOT = BASE_DIR / "staticfiles"). Ejecutarlo en tiempo de build (buildCommand: python manage.py collectstatic --noinput) funciona porque todas las variables del servicio están disponibles para el build; asegúrate de definir SECRET_KEY antes del primer build.
  • Medios / subidas: el disco del contenedor es efímero. Monta un volumen en MEDIA_ROOT (skiffly volume add --mount-path /app/media) o usa almacenamiento compatible con S3 (django-storages con la plantilla minio o un bucket externo).

Workers y tareas programadas#

Los workers de Celery o RQ son un segundo servicio del mismo repositorio con el comando de inicio celery -A mysite worker -l info y sin dominio; Celery beat es un tercero, o un servicio con programación cron cuyo comando de inicio es un comando de management (python manage.py send_digests). Los servicios comparten variables mediante ${{shared.NAME}} o referencias.

Health check#

path("healthz/", lambda r: HttpResponse("ok")) y configura la ruta del health check en /healthz/. Como ALLOWED_HOSTS se comprueba en cada solicitud, la cabecera Host de la sonda (la dirección privada del servicio) debe aceptarse: un middleware al estilo django-healthcheck que responda antes de la comprobación del host, o ALLOWED_HOSTS = ["*"] detrás del proxy de Skiffly, son ambas opciones comunes.

Problemas comunes#

SíntomaSolución
DisallowedHost at /agrega el dominio (y SKIFFLY_PUBLIC_DOMAIN) a ALLOWED_HOSTS
CSRF verification failed. Origin checking failedCSRF_TRUSTED_ORIGINS = ["https://app.example.com"]
FAILED después de 5 minutos, gunicorn registra Listening at: http://127.0.0.1:8000enlaza a 0.0.0.0:$PORT
ModuleNotFoundError: No module named 'mysite'--chdir/paquete incorrecto en el comando de inicio, o el directorio raíz del servicio no es la raíz del proyecto Django
El build de psycopg2 falla (pg_config not found)usa psycopg[binary] / psycopg2-binary
collectstatic falla con SECRET_KEY vacíodefine SECRET_KEY antes de desplegar
Los archivos estáticos dan 404 en producciónDEBUG=False desactiva el servido de estáticos de Django: agrega WhiteNoise o sírvelos desde un CDN
Las subidas desaparecen después de un despliegueMEDIA_ROOT está en el disco efímero: monta un volumen o usa almacenamiento de objetos

Ejemplo#

.skiffly/skiffly.ts
import { defineSkiffly, generate, github, postgres, project, redis, service } from "@skiffly/config";
 
export default defineSkiffly(() => {
  const db = postgres("db");
  const cache = redis("cache");
  const web = service("web", {
    source: github("acme/mysite"),
    start: "python manage.py migrate && python manage.py collectstatic --noinput && gunicorn mysite.wsgi --bind 0.0.0.0:$PORT",
    healthcheck: "/healthz/",
    port: 8000,
    env: { DATABASE_URL: db.env.DATABASE_URL, REDIS_URL: cache.env.REDIS_URL, SECRET_KEY: generate("hex64"), DJANGO_SETTINGS_MODULE: "mysite.settings" },
    domain: true,
  });
  const worker = service("worker", { source: github("acme/mysite"), start: "celery -A mysite worker -l info", env: { DATABASE_URL: db.env.DATABASE_URL, REDIS_URL: cache.env.REDIS_URL, SECRET_KEY: web.env.SECRET_KEY } });
  return project("mysite", { resources: [db, cache, web, worker] });
});

Tutorial: Django + Postgres + Redis. Landing: /deploy/django.