Despliegue de Flask en producción

Avanzado
Flask
Flask
Actualizado: 18/04/2026

Por qué no usar el servidor de desarrollo en producción

El servidor integrado de Flask (flask run / app.run(debug=True)) está diseñado exclusivamente para desarrollo. En producción presenta limitaciones críticas:

Arquitectura de producción Flask: Nginx + Gunicorn + Flask

  • Un solo proceso, un solo hilo: solo puede manejar una petición a la vez
  • Sin recuperación ante fallos: si el proceso cae, la aplicación deja de funcionar
  • Sin gestión de recursos: no limita conexiones, memoria ni tiempo de respuesta
  • Modo debug activado: expone información sensible del sistema en las páginas de error
  • Sin logging de producción: no registra peticiones de forma estructurada
# NUNCA en producción:
if __name__ == '__main__':
    app.run(debug=True)  # Solo para desarrollo local

# En producción, el punto de entrada es solo la instancia de la app:
# gunicorn app:app

Gunicorn: el servidor WSGI de producción

Gunicorn (Green Unicorn) es el servidor WSGI más popular para aplicaciones Flask en producción. Es estable, bien mantenido y fácil de configurar:

pip install gunicorn

Estructura básica de la aplicación para Gunicorn:

# app.py
from flask import Flask

def crear_app(config_name='production'):
    app = Flask(__name__)
    app.config.from_object(f'config.{config_name.title()}Config')

    # Inicializar extensiones
    from app.extensions import db, migrate, login_manager
    db.init_app(app)
    migrate.init_app(app, db)
    login_manager.init_app(app)

    # Registrar blueprints
    from app.blueprints.api import api_bp
    from app.blueprints.web import web_bp
    app.register_blueprint(api_bp, url_prefix='/api')
    app.register_blueprint(web_bp)

    return app

# Punto de entrada para Gunicorn
app = crear_app()

Ejecutar con Gunicorn:

# Forma básica
gunicorn app:app

# Con múltiples workers (recomendado: CPU * 2 + 1)
gunicorn -w 4 app:app

# Con host y puerto específicos
gunicorn -w 4 -b 0.0.0.0:5000 app:app

# Para app factory pattern
gunicorn -w 4 'app:crear_app()'

# Con logging a archivo
gunicorn -w 4 -b 0.0.0.0:5000 \
  --access-logfile /var/log/gunicorn/access.log \
  --error-logfile /var/log/gunicorn/error.log \
  app:app

Configuración de Gunicorn con archivo de configuración

Para entornos de producción, es mejor usar un archivo de configuración de Gunicorn:

# gunicorn.conf.py
import multiprocessing
import os

# Dirección de escucha
bind = '127.0.0.1:5000'

# Número de workers (2-4 x num_cores)
workers = multiprocessing.cpu_count() * 2 + 1

# Tipo de worker (sync para Flask estándar, gevent o uvicorn para async)
worker_class = 'sync'

# Timeout de petición (segundos)
timeout = 30

# Mantener conexiones abiertas
keepalive = 5

# Máximo de peticiones por worker (evita memory leaks)
max_requests = 1000
max_requests_jitter = 100

# Logging
accesslog = '/var/log/gunicorn/access.log'
errorlog = '/var/log/gunicorn/error.log'
loglevel = 'warning'

# Recarga automática en desarrollo (NO en producción)
reload = os.environ.get('FLASK_DEBUG', '0') == '1'

# PID file
pidfile = '/var/run/gunicorn/gunicorn.pid'

Usar el archivo de configuración:

gunicorn -c gunicorn.conf.py app:app

Configuración de la aplicación para producción

# config.py
import os

class Config:
    SECRET_KEY = os.environ.get('SECRET_KEY')
    SQLALCHEMY_TRACK_MODIFICATIONS = False

    @classmethod
    def init_app(cls, app):
        pass

class DevelopmentConfig(Config):
    DEBUG = True
    SQLALCHEMY_DATABASE_URI = os.environ.get('DEV_DATABASE_URL') or \
        'sqlite:///dev.db'

class ProductionConfig(Config):
    DEBUG = False
    TESTING = False
    SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL')

    # Configuraciones de seguridad de producción
    SESSION_COOKIE_SECURE = True
    SESSION_COOKIE_HTTPONLY = True
    SESSION_COOKIE_SAMESITE = 'Lax'
    PREFERRED_URL_SCHEME = 'https'

    @classmethod
    def init_app(cls, app):
        Config.init_app(app)
        # Logging a syslog en producción
        import logging
        from logging.handlers import SysLogHandler
        syslog_handler = SysLogHandler()
        syslog_handler.setLevel(logging.WARNING)
        app.logger.addHandler(syslog_handler)

config = {
    'development': DevelopmentConfig,
    'production': ProductionConfig,
    'default': DevelopmentConfig
}

Variables de entorno en producción (archivo .env o variables del sistema):

# .env (producción - NUNCA subir a git)
SECRET_KEY=tu-clave-super-secreta-de-64-caracteres-minimo
DATABASE_URL=postgresql://usuario:password@localhost:5432/miapp
FLASK_DEBUG=0
REDIS_URL=redis://localhost:6379/0
MAIL_SERVER=smtp.sendgrid.net
MAIL_USERNAME=apikey
MAIL_PASSWORD=tu-api-key-sendgrid

Nginx como proxy inverso

Nginx actúa como proxy inverso delante de Gunicorn, manejando conexiones TLS, archivos estáticos y equilibrio de carga:

# /etc/nginx/sites-available/miapp
server {
    listen 80;
    server_name miapp.com www.miapp.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name miapp.com www.miapp.com;

    # Certificados SSL (Let's Encrypt / Certbot)
    ssl_certificate /etc/letsencrypt/live/miapp.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/miapp.com/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;

    # Archivos estáticos servidos directamente por Nginx (más eficiente)
    location /static {
        alias /var/www/miapp/static;
        expires 30d;
        add_header Cache-Control "public, no-transform";
    }

    # Proxy a Gunicorn
    location / {
        proxy_pass http://127.0.0.1:5000;
        proxy_set_header Host $http_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 10s;
        proxy_read_timeout 30s;

        # Límites de tamaño
        client_max_body_size 16m;
    }
}

Flask debe estar configurado para confiar en los headers del proxy:

# app.py
from werkzeug.middleware.proxy_fix import ProxyFix

app = Flask(__name__)

# En producción con Nginx, corregir los headers de proxy
app.wsgi_app = ProxyFix(
    app.wsgi_app,
    x_for=1,    # Número de proxies que añaden X-Forwarded-For
    x_proto=1,  # Número de proxies que añaden X-Forwarded-Proto
    x_host=1,   # Número de proxies que añaden X-Forwarded-Host
)

Systemd: gestión del proceso

Configura Gunicorn como servicio systemd para que inicie automáticamente y se reinicie ante fallos:

# /etc/systemd/system/miapp.service
[Unit]
Description=Gunicorn para Flask MiApp
After=network.target

[Service]
User=www-data
Group=www-data
WorkingDirectory=/var/www/miapp
Environment="PATH=/var/www/miapp/venv/bin"
EnvironmentFile=/var/www/miapp/.env
ExecStart=/var/www/miapp/venv/bin/gunicorn \
    -c /var/www/miapp/gunicorn.conf.py \
    app:app
ExecReload=/bin/kill -s HUP $MAINPID
KillMode=mixed
TimeoutStopSec=5
PrivateTmp=true
Restart=on-failure
RestartSec=5s

[Install]
WantedBy=multi-user.target

Comandos de gestión del servicio:

# Activar y arrancar el servicio
sudo systemctl enable miapp
sudo systemctl start miapp

# Verificar estado
sudo systemctl status miapp

# Ver logs
sudo journalctl -u miapp -f

# Recargar configuración sin parar el servicio
sudo systemctl reload miapp

# Reiniciar el servicio
sudo systemctl restart miapp

Recarga sin tiempo de caída (zero-downtime)

Gunicorn soporta recarga en caliente enviando la señal SIGHUP, que crea nuevos workers con el código actualizado antes de terminar los anteriores:

# Obtener el PID de Gunicorn
cat /var/run/gunicorn/gunicorn.pid

# Recargar workers sin interrumpir peticiones en curso
kill -HUP $(cat /var/run/gunicorn/gunicorn.pid)

# O usando systemctl
sudo systemctl reload miapp

Este enfoque garantiza que las peticiones en curso se completen antes de reemplazar los workers, consiguiendo zero-downtime deployments.

Fuentes y referencias

Documentación oficial y recursos externos para profundizar en Flask

Documentación oficial de Flask
Alan Sastre - Autor del tutorial

Alan Sastre

Ingeniero de Software y formador, CEO en CertiDevs

Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Flask es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.

Más tutoriales de Flask

Explora más contenido relacionado con Flask y continúa aprendiendo con nuestros tutoriales gratuitos.

Aprendizajes de esta lección

Comprender por qué no se debe usar el servidor de desarrollo de Flask en producción. Configurar Gunicorn como servidor WSGI de producción para Flask. Usar Nginx como proxy inverso delante de Gunicorn. Gestionar variables de entorno y configuraciones de producción de forma segura. Configurar el número de workers y opciones de rendimiento de Gunicorn.