Deployment de una app web Python en AWS EC2. Flask + Gunicorn + systemd + Nginx
Guía para desplegar una aplicación web construida con Python + Flask, incluyendo casos con Dash como framework de visualización en una instancia AWS EC2 con Ubuntu Server, usando una arquitectura estándar de producción:
- Flask: como app WSGI.
- Gunicorn: como servidor WSGI.
- systemd: como gestor de proceso.
- Nginx: como reverse proxy.
También incluye una sección de diagnóstico de errores reales como 500/502/504, timeouts, OOM-kill y una checklist final para que puedas repetir el proceso con consistencia.
¿Qué es AWS EC2? Ventajas y alternativas
Amazon EC2 (Elastic Compute Cloud) es un servicio de AWS que te permite crear y administrar servidores virtuales en la nube (instancias). En la práctica, EC2 es un “Linux/Windows remoto” donde tú controlas casi todo: sistema operativo, paquetes, firewall, procesos, configuración de Nginx, etc.
Ventajas principales
- Control total del sistema: puedes instalar lo que quieras (PostgreSQL, Redis, Nginx, librerías del sistema, etc.).
- Escalabilidad: puedes cambiar el tipo de instancia, agregar discos, balanceadores, auto-scaling, etc.
- Ecosistema AWS: integración natural con Route 53 (DNS), IAM (permisos), S3 (archivos), RDS (bases de datos), CloudWatch (logs), etc.
- Costos flexibles: pagas por sólo el uso y hay disponibles capas free tier como instancias pequeñas para empezar un proyecto.
Desventajas o costos ocultos de EC2
- Más responsabilidad operativa: tú configuras todo (seguridad, actualizaciones, backups, hardening, etc.).
- No es serverless: requiere mantenimiento (parches, reinicios, monitoreo).
- Riesgo de errores de configuración: un security group mal definido o una mala configuración de Nginx puede bloquear tu app o exponer tu servidor.
Alternativas dentro y fuera de AWS
Dependiendo de tu objetivo y si prefieres rapidez o control puedes considerar:
- AWS Lightsail: más simple que EC2, ideal para primeros despliegues; menos flexible pero más directo.
- AWS Elastic Beanstalk: PaaS que automatiza despliegue, scaling y monitoreo; tú subes el código y AWS maneja gran parte de la operación.
- AWS ECS/Fargate: contenedores en producción; excelente para escalado y deployments reproducibles con Docker.
- AWS App Runner: despliegue administrado de servicios web (contenedores o repositorios), con menos fricción que ECS.
- Heroku / Render / Railway: experiencias PaaS muy simples, buena opción para proyectos personales/portafolio.
- DigitalOcean Droplets: similar a EC2, interfaz más simple.
- Google Compute Engine / Azure Virtual Machines: equivalentes a EC2 en otras nubes.
¿Qué es Flask brevemente?
Flask es un microframework web de Python (ligero y flexible) para crear aplicaciones web y APIs. Se caracteriza por:
- Un núcleo pequeño y extensible donde tú eliges librerías para auth, ORM, etc.
- Enfoque claro en rutas, vistas y plantillas con Jinja2.
- Ideal para aplicaciones web de tamaño pequeño a mediano, APIs, prototipos y proyectos que crecen con el tiempo.
En producción, Flask normalmente se ejecuta con un servidor WSGI como Gunicorn o uWSGI, y se coloca detrás de un reverse proxy como Nginx.
Arquitectura recomendada para producción
Una arquitectura típica y estable para el futuro es:
- El usuario entra por HTTP/HTTPS al servidor.
- Nginx recibe la petición y actúa como reverse proxy.
- Nginx reenvía la petición a Gunicorn (por socket UNIX o TCP).
- Gunicorn ejecuta tu app Flask/Dash y devuelve la respuesta a Nginx.
- Nginx entrega la respuesta final al navegador.
Ventajas de Nginx delante de Gunicorn:
- Maneja HTTPS (TLS) y certificados con buena performance.
- Gestiona timeouts, buffers y tamaño máximo de carga (payload).
- Puede servir archivos estáticos eficientemente (CSS/JS/imagenes).
- Evita exponer directamente Gunicorn a Internet.
Requisitos previos
Antes de iniciar, asegúrate de tener:
- Una cuenta de AWS con permisos para crear instancias EC2 y security groups.
- Un Key Pair (.pem) para autenticación SSH.
- Conocimientos básicos de terminal/SSH.
- Un repositorio con tu aplicación:
- Un punto de entrada claro como
main.pyoapp.py. - Un objeto WSGI exportado como app, por ejemplo
app = Flask(__name__). - Un archivo
requirements.txtactualizado.
Convención utilizada en el artículo:
- Usuario remoto:
ubuntu. - Ruta del proyecto:
/home/ubuntu/APP. - Módulo y app WSGI:
main:app. - Puerto interno de Gunicorn:
127.0.0.1:8000(usado para pruebas). - Socket UNIX final:
/home/ubuntu/APP/flaskapp.sock.
Paso a paso del deployment en EC2
1. Crear la instancia EC2.
- En AWS Console: EC2 → Launch instance.
- Selecciona una AMI: Ubuntu Server 22.04 LTS (recomendado debido a la estabilidad).
- Elige un tipo de instancia, por ejemplo t2.micro, t3.micro, etc.
- Crea o selecciona un Key Pair. Descarga el
.pemy guárdalo en un lugar seguro. - Configura un Security Group (reglas de entrada). Como mínimo:
- Lanza la instancia y espera a que esté en estado running.
- Copia su Public IPv4 address.
SSH (22) TCP My IP/32
HTTP (80) TCP 0.0.0.0/0
HTTPS (443) TCP 0.0.0.0/0
2. Conectar por SSH.
En tu máquina local:
- Ajusta permisos del
.pem: - Conéctate:
chmod 400 ~/Downloads/mi-llave.pem
ssh -i ~/Downloads/mi-llave.pem ubuntu@IP_PUBLICA
3. Actualizar el sistema.
En la instancia:
sudo apt update && sudo apt upgrade -y
Si actualizas kernel o servicios críticos, reinicia:
sudo reboot
Luego reconecta por medio de SSH.
4. Instalar dependencias base.
Instalar herramientas esenciales:
sudo apt install -y python3-venv python3-pip git nginx
Y verifica versiones:
python3 --version
pip3 --version
git --version
nginx -v
5. Clonar el repositorio y preparar la app.
Ve a tu HOME y clona:
cd ~
git clone https://github.com/TU_USUARIO/TU_REPO.git APP
cd APP
Si tu app necesita variables de entorno (por ejemplo secretos, strings de conexión, API keys), define una estrategia clara:
-
.envlocal (nunca lo subas a Git). - Variables en
systemd(recomendado en este enfoque). - AWS SSM Parameter Store / Secrets Manager (más robusto, más AWS-native).
6. Crear y activar el entorno virtual.
Crea el venv dentro del proyecto:
python3 -m venv venv
source venv/bin/activate
Confirma que estás usando el Python del venv:
which python
python --version
7. Instalar dependencias Python.
Actualiza pip e instala requirements:
pip install --upgrade pip
pip install -r requirements.txt
Si alguna dependencia exige una versión de Python que tu instancia no tiene, tendrás que:
- Ajustar versiones en
requirements.txt, o - Instalar una versión más nueva de Python (y recrear el venv)
Si tu app usa librerías con dependencias del sistema como psycopg2, lxml, pillow, tal
vez necesites apt install adicional.
8. Prueba local con Gunicorn.
Antes de meter systemd y Nginx, valida que Gunicorn levanta tu app, para esto, ejecuta dentro del venv:
python -m gunicorn --bind 127.0.0.1:8000 main:app
En otra sesión SSH o en la misma con otra terminal, prueba:
curl http://127.0.0.1:8000
Si ves HTML o respuesta válida, tu app WSGI está bien expuesta.
Detén Gunicorn con Ctrl + C cuando termines la prueba.
9. Crear servicio systemd.
systemd permite que tu app:
- Se levante al iniciar la instancia.
- Reinicie si se cae.
- Deje logs accesibles con
journalctlpara investigar incidencias.
Crea el servicio:
sudo nano /etc/systemd/system/flaskapp.service
Ejemplo de configuración que ajusta rutas y nombre de módulo/app:
[Unit]
Description=Gunicorn service para APP (Flask/Dash)
After=network.target
[Service]
User=ubuntu
Group=www-data
WorkingDirectory=/home/ubuntu/APP
# Variables de entorno (ejemplos):
# Environment="FLASK_ENV=production"
# Environment="APP_SECRET_KEY=..."
# Environment="DATABASE_URL=postgresql://..."
# Environment="PYTHONUNBUFFERED=1"
Environment="PATH=/home/ubuntu/APP/venv/bin"
ExecStart=/home/ubuntu/APP/venv/bin/gunicorn \
--workers 2 \
--timeout 120 \
--bind unix:/home/ubuntu/APP/flaskapp.sock \
main:app
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
Aplica cambios:
sudo systemctl daemon-reload
sudo systemctl start flaskapp.service
sudo systemctl enable flaskapp.service
sudo systemctl status flaskapp.service
Si falla, revisa logs:
sudo journalctl -u flaskapp.service -n 50 --no-pager
10. Configurar Nginx.
Nginx recibirá tráfico en el puerto 80 y 443 si hay HTTPS y lo enviará al socket UNIX.
Crea un “server block”:
sudo nano /etc/nginx/sites-available/flaskapp
Contenido recomendado:
server {
listen 80;
server_name _;
# Ajusta si tu app envía/recibe payloads grandes (Dash + dcc.Store)
client_max_body_size 50M;
location / {
include proxy_params;
proxy_pass http://unix:/home/ubuntu/APP/flaskapp.sock:/;
# Websocket/upgrade headers (útil en algunas apps interactivas)
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Timeouts (útiles si tu app tarda en responder por cálculos pesados)
proxy_connect_timeout 120s;
proxy_send_timeout 120s;
proxy_read_timeout 120s;
}
}
Habilita el sitio y deshabilita el default:
sudo ln -sf /etc/nginx/sites-available/flaskapp /etc/nginx/sites-enabled/flaskapp
sudo rm -f /etc/nginx/sites-enabled/default
Verifica y recarga:
sudo nginx -t
sudo systemctl reload nginx
Prueba desde la propia instancia:
curl http://127.0.0.1
Si responde, prueba desde tu navegador: http://IP_PUBLICA.
11. Abrir puertos y firewall UFW.
En Ubuntu, UFW puede estar deshabilitado por defecto. Si decides usarlo, una configuración típica:
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable
sudo ufw status
12. Dominio, Elastic IP y HTTPS Certbot.
La IP pública por defecto puede cambiar al reiniciar/terminar la instancia. Una Elastic IP (EIP) te da una IP fija, para conseguirla dirìgete en AWS Console:
- EC2 → Elastic IPs → Allocate Elastic IP.
- Asócialo a tu instancia
Para una página personal o blog como este, lo normal es usar dominio + HTTPS con Certbot.
Instala Certbot:
sudo apt install -y certbot python3-certbot-nginx
Ejecuta reemplazando con tu dominio:
sudo certbot --nginx -d tu_dominio.com -d www.tu_dominio.com
Certbot editará Nginx y agregará bloques para 443, además de redirección a HTTPS si lo eliges.
Renovación automática para verificar:
sudo systemctl status certbot.timer
Operación y mantenimiento
Logs y monitoreo.
Estado del servicio:
sudo systemctl status flaskapp.service
Logs de Gunicorn vía systemd:
sudo journalctl -u flaskapp.service -f
Logs de Nginx:
sudo tail -n 100 /var/log/nginx/error.log
sudo tail -n 100 /var/log/nginx/access.log
Actualizar la aplicación.
Traer cambios desde Git:
cd /home/ubuntu/APP
git pull
Si hay cambios en las dependencias, actualizar el requirements.txt:
source venv/bin/activate
pip install -r requirements.txt
deactivate
Reiniciar servicio:
sudo systemctl restart flaskapp.service
sudo systemctl status flaskapp.service
Recargar Nginx si cambiaste su config:
sudo nginx -t && sudo systemctl reload nginx
Problemas comunes y soluciones: 500/502/504, timeouts, OOM
Esta sección resume un patrón típico al desplegar Flask + Dash en EC2. En local todo funciona muy bien, pero en producción aparecen errores en callbacks (/_dash-update-component), timeouts o respuestas 502/504.
1. 500 Internal Server Error en callbacks de Dash.
Revisa Logs de Gunicorn:
sudo journalctl -u flaskapp.service -n 100 --no-pager
Las causas típicas son:
- Excepciones dentro del callback.
- Errores de importación en producción como paths diferentes.
- Falta de variables de entorno como secrets, credenciales, etc.
Acciones recomendadas:
- Asegura que las variables necesarias se declaran en
systemdconEnvironment="...". - Imprime/monitorea errores en el backend (logging).
2. 502 Bad Gateway o 504 Gateway Timeout.
Las causas típicas son:
- Gunicorn no está levantado o el socket no existe.
- Nginx no tiene permisos para acceder al socket o al directorio.
- El callback tarda demasiado y Nginx corta la conexión.
Acciones recomendadas:
- Validar servicio:
- Validar que el socket existe:
- Aumentar timeouts de Nginx si tu app tarda:
- Aumentar timeout de Gunicorn en
ExecStart: - Recargar servicios:
sudo systemctl status flaskapp.service
ls -l /home/ubuntu/APP/flaskapp.sock
proxy_connect_timeout 120s;
proxy_send_timeout 120s;
proxy_read_timeout 120s;
--timeout 120
sudo systemctl daemon-reload
sudo systemctl restart flaskapp.service
sudo nginx -t && sudo systemctl reload nginx
3. OOM-kill por memoria insuficiente.
Las causas típicas son:
- El sistema mata workers de Gunicorn.
- Lo notarás en logs (journalctl) y con mensajes relacionados a memoria.
Acciones recomendadas:
- Reduce workers a 1 o 2 en instancias pequeñas.
- Considera agregar swap si estás en una instancia micro:
sudo fallocate -l 1G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
Swap ayuda a estabilidad, pero no es sustituto de RAM real. Si el uso de memoria es alto por diseño combinando pandas + grandes DataFrames, una instancia con más RAM suele ser la solución definitiva.
4. proxy_pass mal formado con socket UNIX.
Al usar sockets UNIX, el formato recomendado es:
proxy_pass http://unix:/home/ubuntu/APP/flaskapp.sock:/;
El sufijo :/ suele evitar errores sutiles en el routing del proxy.
5. Permisos y 502 por acceso al socket.
Asegura permisos adecuados en directorios padre, por ejemplo:
sudo chown -R ubuntu:www-data /home/ubuntu/APP
sudo chmod 750 /home/ubuntu/APP
sudo chmod o+x /home/ubuntu
sudo systemctl reload nginx
Checklist final
Antes de considerar “terminado” el deployment, valida:
- Security Group: SSH limitado, HTTP/HTTPS abiertos según necesidad.
- SSH con key
.pemfunciona y permisos del archivo están correctos. sudo apt update && sudo apt upgrade -ycompletado.nginx -vypython3 --versioncorrectos.- Repo clonado en
/home/ubuntu/APP. venvcreado y activable.pip install -r requirements.txtsin errores.python -m gunicorn --bind 127.0.0.1:8000 main:appresponde concurl.- Servicio systemd activo y habilitado
sudo systemctl status flaskapp.service. - Nginx OK:
sudo nginx -t.curl http://127.0.0.1.- Navegador
http://IP_PUBLICA. - Ajustes de timeouts si hay callbacks pesados.
client_max_body_sizeajustado si hay payloads grandes.- Ajustes de timeouts si hay callbacks pesados.
- Opcionalmente: EIP + dominio + HTTPS con Certbot.