Objetivos de aprendizaje
Después de leer este artículo podrás:
- Elegir el servidor de producción adecuado para cada framework
- Preparar e iniciar una app FastAPI, Flask o Django fuera de tu computadora
- Corregir los errores más comunes de una app Python recién publicada
En esta página
Los tres frameworks traen un servidor integrado que arranca con un comando, y ahí está el error más común: llevar ese servidor a producción. Está hecho para desarrollar. Para publicar, la app necesita un servidor de aplicación (Uvicorn o Gunicorn) que escuche en la interfaz correcta y lea la configuración del entorno.
Esta guía muestra lo mínimo de cada framework, el comando de producción correspondiente y los ajustes que exige Django. El razonamiento general para publicar una API está en cómo hospedar una API.
¿Qué servidor de producción usa cada framework?#
La diferencia está en la interfaz entre el framework y el servidor:
| Framework | Interfaz | Servidor de producción | Comando típico |
|---|---|---|---|
| FastAPI | ASGI | Uvicorn (viene con fastapi[standard]) |
fastapi run o uvicorn main:app |
| Flask | WSGI | Gunicorn | gunicorn 'app:app' |
| Django | WSGI (o ASGI) | Gunicorn | gunicorn misitio.wsgi |
Gunicorn no corre en Windows (solo en WSL). Los servidores de producción suelen ser Linux, así que esto afecta solo a quien prueba en local.
¿Cómo hospedar una app FastAPI?#
Instálala con los extras estándar, que incluyen Uvicorn:
pip install "fastapi[standard]"main.py:
from fastapi import FastAPI
app = FastAPI()
@app.get("/health")
def health():
return {"status": "ok"}En producción, sin --reload (la documentación de FastAPI advierte que consume más recursos y es más inestable):
uvicorn main:app --host 0.0.0.0 --port "${PORT:-8000}" --workers 2main:app significa "el objeto app del archivo main.py". Cada worker es un proceso aparte, y el código de inicio se ejecuta una vez por worker. fastapi run main.py es la alternativa corta: también escucha en 0.0.0.0, por defecto en el puerto 8000.
¿Cómo hospedar una app Flask?#
pip install flask gunicornapp.py:
import os
from flask import Flask
app = Flask(__name__)
app.config["SECRET_KEY"] = os.environ["SECRET_KEY"]
@app.get("/health")
def health():
return {"status": "ok"}Inícialo con Gunicorn:
gunicorn -w 4 -b "0.0.0.0:${PORT:-8000}" 'app:app'El argumento app:app sigue el formato {módulo}:{variable}. Con el patrón application factory, usa 'app:create_app()'. -w define el número de procesos, y la documentación de Flask sugiere CPU * 2 como punto de partida. Sin -b, Gunicorn escucha en 0.0.0.0 en el puerto de la variable PORT, si existe; sin PORT, escucha solo en 127.0.0.1:8000, y la app parece caída. Pasar -b deja la dirección explícita en ambos casos.
¿Cómo hospedar una app Django?#
Django exige más ajustes: con DEBUG activado, expone código y configuración, y en producción nadie sirve los archivos estáticos por ti.
pip install django gunicorn whitenoiseEn settings.py, lee del entorno lo que cambia entre desarrollo y producción:
import os
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent
SECRET_KEY = os.environ["SECRET_KEY"]
DEBUG = os.environ.get("DEBUG") == "1"
ALLOWED_HOSTS = os.environ["ALLOWED_HOSTS"].split(",")
CSRF_TRUSTED_ORIGINS = [
o for o in os.environ.get("CSRF_TRUSTED_ORIGINS", "").split(",") if o
]
STATIC_URL = "static/"
STATIC_ROOT = BASE_DIR / "staticfiles"
STORAGES = {
"default": {"BACKEND": "django.core.files.storage.FileSystemStorage"},
"staticfiles": {
"BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage"
},
}
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"whitenoise.middleware.WhiteNoiseMiddleware", # justo después de SecurityMiddleware
# ... demás middlewares
]Con ALLOWED_HOSTS=miapp.ejemplo.com y CSRF_TRUSTED_ORIGINS=https://miapp.ejemplo.com, el dominio público queda permitido. CSRF_TRUSTED_ORIGINS lleva el esquema (https://) y vale para peticiones POST que vienen de otro origen.
Antes de iniciar, reúne los estáticos y ejecuta las migraciones:
python manage.py collectstatic --noinput
python manage.py migrate
gunicorn misitio.wsgi -b "0.0.0.0:${PORT:-8000}"WhiteNoise sirve los archivos de STATIC_ROOT directamente desde el proceso Python, así que no necesitas un servidor web aparte para ellos. Por último, ejecuta python manage.py check --deploy con la configuración de producción: señala lo que falta.
Si el proxy del hosting termina el HTTPS y reenvía HTTP a Django, request.is_secure() devuelve False. La salida es SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https"), pero la documentación de Django solo lo permite si el proxy descarta el X-Forwarded-Proto que envía el cliente y solo lo define en peticiones que llegaron por HTTPS. Sin esa garantía, déjalo en None.
¿Cómo saber si el deploy funcionó?#
Prueba desde fuera del servidor, con la dirección pública:
curl -i https://miapp.ejemplo.com/healthEsperas HTTP/2 200 y el JSON {"status":"ok"}. En Django, crea una vista simple en /health o abre la página de inicio. Si la respuesta es otra, lee los logs del proceso: el traceback de Python muestra la línea exacta.
¿Cuáles son los errores más comunes?#
La conexión es rechazada o expira. El servidor escucha en 127.0.0.1 o en un puerto distinto del expuesto. Pasa --host 0.0.0.0 (Uvicorn) o -b 0.0.0.0:PUERTO (Gunicorn).
Django responde Bad Request (400). El dominio no está en ALLOWED_HOSTS. Con DEBUG = False y la lista sin el dominio (o vacía), Django responde 400 a todas las peticiones.
Un formulario de Django falla con 403 (CSRF verification failed). El origen del sitio no está en CSRF_TRUSTED_ORIGINS, o Django no sabe que la conexión original era HTTPS. Revisa la lista y el encabezado del proxy.
El CSS del admin de Django no carga. Faltó collectstatic o WhiteNoise quedó fuera de MIDDLEWARE. Ejecuta collectstatic en cada deploy.
KeyError: 'SECRET_KEY'. La variable de entorno no se definió en el servidor. El fallo es a propósito: mejor no arrancar que arrancar sin secreto. Mira qué son las variables de entorno.
ModuleNotFoundError. Una dependencia quedó fuera de requirements.txt. Genera el archivo con pip freeze > requirements.txt dentro del entorno virtual del proyecto.
La app se reinicia sola o va lenta. Demasiados workers para poca RAM: cada uno es un proceso con su propia copia de la app. Baja --workers o aumenta la memoria.
¿Qué más cuidar antes de abrirla al público?#
- Base de datos: un archivo SQLite local junto a la app es frágil con varios workers y cuando se cambia el disco del servidor. Para uso real, prefiere PostgreSQL o MySQL; mira cómo hospedar una base de datos.
- Migraciones: ejecuta
migrate(Django) o tu sistema de migración en cada deploy, antes de enviar tráfico a la versión nueva. - CORS: en FastAPI, usa
CORSMiddlewarecon orígenes exactos; en Flask y Django, los paquetesflask-corsydjango-cors-headers. - Dominio propio: apunta el dominio a tu app y actualiza
ALLOWED_HOSTSal mismo tiempo.
Cómo hacerlo en Square Cloud#
Los comandos de producción de esta guía se convierten en el comando de inicio de la aplicación en Square Cloud. Subes el proyecto como ZIP en el panel, con la CLI, desde GitHub o con la extensión de VS Code; al iniciar, la plataforma ejecuta pip install -r requirements.txt y define la variable PORT. El archivo squarecloud.app es opcional: el panel lo genera en el deploy. En el campo de comando de inicio del formulario, usa el servidor de tu framework:
- FastAPI:
uvicorn main:app --host 0.0.0.0 --port $PORT --workers 2 - Flask:
gunicorn -w 2 -b "0.0.0.0:$PORT" 'app:app' - Django:
python manage.py collectstatic --noinput && python manage.py migrate && gunicorn misitio.wsgi -b "0.0.0.0:$PORT"
Como no hay paso de build, Django reúne los estáticos y aplica las migraciones ahí mismo, en cada inicio. Todos los planes le dan a la aplicación web un subdominio *.squareweb.app con TLS automático, y ese host es el que va en las variables de Django: ALLOWED_HOSTS=miapp.squareweb.app y CSRF_TRUSTED_ORIGINS=https://miapp.squareweb.app, junto a SECRET_KEY. Las variables se definen en el panel, ya existen durante la instalación y valen desde el siguiente reinicio, sin rebuild.
Cada worker ocupa memoria, y una aplicación web usa al menos 512 MB de la RAM del plan; el Hobby empieza en R$12,49 al mes con 1 GB. Si los workers se quedan sin memoria, la aplicación se detiene hasta que aumentes su RAM o reduzcas los workers. El reinicio automático, activado por defecto en el deploy desde el panel, solo cubre los procesos que terminan con error. El disco persiste entre deploys, pero SQLite sigue siendo frágil con varios workers: el hosting de PostgreSQL y MySQL empieza en Standard (desde R$44,99 al mes), en el mismo datacenter que tus aplicaciones.
Consulta los planes en hosting de APIs y el runtime en Python.
Hosting de APIs
APIs en contenedores siempre encendidos, sin cold start
Preguntas frecuentes
¿Puedo usar el servidor de desarrollo de Flask o el runserver de Django en producción?
No. Sirven para desarrollar, no para carga real ni para seguridad. En producción usa Gunicorn (Flask y Django) o Uvicorn (FastAPI).
¿Cuántos workers debo usar?
Empieza con pocos y ajusta según el uso real. La documentación de Flask sugiere partir de CPU * 2 con Gunicorn, pero cada worker es un proceso con su propia memoria, así que el límite de RAM también cuenta.
¿Por qué Django responde 400 (Bad Request) después del deploy?
Con DEBUG desactivado, Django solo acepta los hosts listados en ALLOWED_HOSTS. Agrega a la lista el host que aparece en la URL.
¿Necesito Nginx delante de Gunicorn?
En un servidor propio (VPS), la documentación de Flask recomienda un proxy inverso delante de Gunicorn. En plataformas que ya entregan HTTPS y enrutamiento, esa capa ya viene incluida.
Más en APIs
Ver los 15 artículos- Cómo hospedar una APIHospedar una API es ejecutar su servidor HTTP las 24 horas en una dirección pública con HTTPS. Guía paso a paso con Node.js y Python, pruebas y errores comunes.
- Cómo hospedar una aplicación Node.jsPara hospedar una aplicación Node.js, prepara el package.json, lee el puerto y los secretos del entorno, elige una versión LTS y ejecútala con node. Paso a paso.
- Qué es un cron job y cómo programar tareasUn cron job es una tarea que corre sola en horarios definidos por una expresión de 5 campos. Mira sintaxis, ejemplos, errores comunes y cómo programar en Node.js.
- Checklist para llevar tu API a producciónChecklist para llevar una API a producción: variables de entorno, health check, apagado limpio, errores estándar, autenticación, rate limit, CORS y logs.