Python es el lenguaje más popular para backend, data science y APIs. Pero ejecutar python app.py en producción no es suficiente.
Esta guía te enseña a desplegar Django y Flask en tu VPS de forma profesional.
Instalación de Python
Python del sistema
# Ubuntu 22.04+ ya incluye Python 3.10+
python3 --version
# Instalar pip y venv
sudo apt update
sudo apt install python3-pip python3-venv python3-dev -y
Pyenv (múltiples versiones)
# Dependencias
sudo apt install build-essential libssl-dev zlib1g-dev \
libbz2-dev libreadline-dev libsqlite3-dev curl \
libncursesw5-dev xz-utils tk-dev libxml2-dev \
libxmlsec1-dev libffi-dev liblzma-dev -y
# Instalar pyenv
curl https://pyenv.run | bash
# Añadir a ~/.bashrc
echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bashrc
echo 'command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bashrc
echo 'eval "$(pyenv init -)"' >> ~/.bashrc
source ~/.bashrc
# Instalar Python
pyenv install 3.12.0
pyenv global 3.12.0
Entornos virtuales
Crear entorno virtual
# Crear directorio del proyecto
mkdir -p /var/www/mi-app
cd /var/www/mi-app
# Crear venv
python3 -m venv venv
# Activar
source venv/bin/activate
# Verificar
which python
# /var/www/mi-app/venv/bin/python
# Desactivar
deactivate
Instalar dependencias
# Con venv activado
pip install -r requirements.txt
# O específicas
pip install django gunicorn psycopg2-binary
requirements.txt
django==5.0
gunicorn==21.2.0
psycopg2-binary==2.9.9
python-dotenv==1.0.0
whitenoise==6.6.0
redis==5.0.1
celery==5.3.4
# Generar requirements.txt
pip freeze > requirements.txt
Estructura de proyecto
Django
/var/www/mi-app/
├── venv/
├── mi_proyecto/
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ ├── wsgi.py
│ └── asgi.py
├── apps/
├── static/
├── media/
├── templates/
├── manage.py
├── requirements.txt
├── .env
└── gunicorn.conf.py
Flask
/var/www/mi-app/
├── venv/
├── app/
│ ├── __init__.py
│ ├── routes.py
│ └── models.py
├── wsgi.py
├── config.py
├── requirements.txt
├── .env
└── gunicorn.conf.py
Gunicorn: WSGI Server
Gunicorn es el servidor WSGI estándar para Python en producción.
Instalar
source venv/bin/activate
pip install gunicorn
Probar manualmente
# Django
gunicorn mi_proyecto.wsgi:application --bind 0.0.0.0:8000
# Flask
gunicorn wsgi:app --bind 0.0.0.0:8000
Archivo wsgi.py (Flask)
# wsgi.py
from app import create_app
app = create_app()
if __name__ == "__main__":
app.run()
gunicorn.conf.py
# gunicorn.conf.py
import multiprocessing
# Binding
bind = "127.0.0.1:8000"
# Workers
workers = multiprocessing.cpu_count() * 2 + 1
worker_class = "sync"
threads = 2
# Timeouts
timeout = 120
keepalive = 5
# Logging
accesslog = "/var/log/gunicorn/access.log"
errorlog = "/var/log/gunicorn/error.log"
loglevel = "info"
# Process naming
proc_name = "mi-app"
# Daemon (no usar con systemd)
daemon = False
# Reload (solo desarrollo)
reload = False
# Crear directorio de logs
sudo mkdir -p /var/log/gunicorn
sudo chown www-data:www-data /var/log/gunicorn
# Ejecutar con config
gunicorn -c gunicorn.conf.py mi_proyecto.wsgi:application
Systemd Service
Crear servicio
sudo nano /etc/systemd/system/mi-app.service
[Unit]
Description=Mi App Python
After=network.target
[Service]
User=www-data
Group=www-data
WorkingDirectory=/var/www/mi-app
Environment="PATH=/var/www/mi-app/venv/bin"
EnvironmentFile=/var/www/mi-app/.env
ExecStart=/var/www/mi-app/venv/bin/gunicorn \
--config gunicorn.conf.py \
mi_proyecto.wsgi:application
ExecReload=/bin/kill -s HUP $MAINPID
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
Gestionar servicio
# Recargar systemd
sudo systemctl daemon-reload
# Habilitar al arranque
sudo systemctl enable mi-app
# Iniciar
sudo systemctl start mi-app
# Ver estado
sudo systemctl status mi-app
# Reiniciar
sudo systemctl restart mi-app
# Ver logs
sudo journalctl -u mi-app -f
Nginx como Proxy
Configuración
# /etc/nginx/sites-available/mi-app
upstream django {
server 127.0.0.1:8000;
}
server {
listen 80;
server_name tudominio.com www.tudominio.com;
# Logs
access_log /var/log/nginx/mi-app.access.log;
error_log /var/log/nginx/mi-app.error.log;
# Archivos estáticos
location /static/ {
alias /var/www/mi-app/static/;
expires 30d;
add_header Cache-Control "public, immutable";
}
location /media/ {
alias /var/www/mi-app/media/;
expires 30d;
}
# Proxy a Gunicorn
location / {
proxy_pass http://django;
proxy_set_header Host $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;
# Timeouts
proxy_connect_timeout 60s;
proxy_read_timeout 60s;
}
}
# Habilitar
sudo ln -s /etc/nginx/sites-available/mi-app /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
# SSL
sudo certbot --nginx -d tudominio.com -d www.tudominio.com
Variables de entorno
Archivo .env
# /var/www/mi-app/.env
DEBUG=False
SECRET_KEY=tu-clave-secreta-muy-larga
ALLOWED_HOSTS=tudominio.com,www.tudominio.com
DATABASE_URL=postgresql://user:pass@localhost:5432/db
REDIS_URL=redis://localhost:6379/0
Django settings.py
import os
from dotenv import load_dotenv
load_dotenv()
DEBUG = os.getenv('DEBUG', 'False') == 'True'
SECRET_KEY = os.getenv('SECRET_KEY')
ALLOWED_HOSTS = os.getenv('ALLOWED_HOSTS', '').split(',')
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': os.getenv('DB_NAME'),
'USER': os.getenv('DB_USER'),
'PASSWORD': os.getenv('DB_PASSWORD'),
'HOST': os.getenv('DB_HOST', 'localhost'),
'PORT': os.getenv('DB_PORT', '5432'),
}
}
Flask config.py
import os
from dotenv import load_dotenv
load_dotenv()
class Config:
SECRET_KEY = os.getenv('SECRET_KEY')
SQLALCHEMY_DATABASE_URI = os.getenv('DATABASE_URL')
DEBUG = os.getenv('DEBUG', 'False') == 'True'
Archivos estáticos (Django)
Configurar
# settings.py
STATIC_URL = '/static/'
STATIC_ROOT = '/var/www/mi-app/static/'
MEDIA_URL = '/media/'
MEDIA_ROOT = '/var/www/mi-app/media/'
# WhiteNoise (alternativa sin Nginx para estáticos)
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware',
# ...
]
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
Recolectar
source venv/bin/activate
python manage.py collectstatic --noinput
Migraciones de base de datos
source venv/bin/activate
# Django
python manage.py makemigrations
python manage.py migrate
# Flask con Flask-Migrate
flask db init
flask db migrate
flask db upgrade
Deploy automatizado
Script de deploy
#!/bin/bash
# /root/scripts/deploy-python.sh
APP_DIR="/var/www/mi-app"
cd $APP_DIR
# Pull cambios
git pull origin main
# Activar venv
source venv/bin/activate
# Instalar dependencias
pip install -r requirements.txt
# Migraciones
python manage.py migrate --noinput
# Estáticos
python manage.py collectstatic --noinput
# Reiniciar
sudo systemctl restart mi-app
echo "Deploy completado: $(date)"
Permisos
# Propietario
sudo chown -R www-data:www-data /var/www/mi-app
# Permisos
sudo chmod -R 755 /var/www/mi-app
sudo chmod 600 /var/www/mi-app/.env
Celery (tareas async)
Instalar
pip install celery redis
celery.py (Django)
# mi_proyecto/celery.py
import os
from celery import Celery
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mi_proyecto.settings')
app = Celery('mi_proyecto')
app.config_from_object('django.conf:settings', namespace='CELERY')
app.autodiscover_tasks()
Servicio Celery
# /etc/systemd/system/celery.service
[Unit]
Description=Celery Worker
After=network.target
[Service]
User=www-data
Group=www-data
WorkingDirectory=/var/www/mi-app
Environment="PATH=/var/www/mi-app/venv/bin"
EnvironmentFile=/var/www/mi-app/.env
ExecStart=/var/www/mi-app/venv/bin/celery \
-A mi_proyecto worker \
--loglevel=info
Restart=on-failure
[Install]
WantedBy=multi-user.target
Problemas comunes
”ModuleNotFoundError”
# Verificar que venv está activado
which python
# Debe mostrar /var/www/mi-app/venv/bin/python
# Si no, activar
source /var/www/mi-app/venv/bin/activate
pip install -r requirements.txt
Error 502
# Verificar Gunicorn
sudo systemctl status mi-app
sudo journalctl -u mi-app -n 50
# Verificar socket/puerto
ss -tlnp | grep 8000
Permisos de archivos
# Si el servicio no puede escribir
sudo chown -R www-data:www-data /var/www/mi-app/media
sudo chmod -R 775 /var/www/mi-app/media
Migraciones fallan
# Verificar conexión a BD
python manage.py dbshell
# Si hay conflictos
python manage.py migrate --fake-initial
Preguntas frecuentes
¿Gunicorn o uWSGI?
Ambos son excelentes. Gunicorn es más simple de configurar y suficiente para la mayoría de casos. uWSGI tiene más opciones pero es más complejo. Para empezar, Gunicorn es la mejor opción.
¿Cuántos workers de Gunicorn necesito?
La fórmula recomendada es (2 x núcleos) + 1. Para un VPS de 2 cores, 5 workers. Ajusta según memoria disponible, cada worker consume RAM adicional.
¿Por qué usar virtualenv en producción?
Aísla las dependencias de tu proyecto del sistema. Evita conflictos entre proyectos y permite tener versiones específicas de cada paquete.
¿Django o Flask para producción?
Ambos son excelentes. Django tiene más incluido (admin, ORM, auth). Flask es más ligero y flexible. Elige según el proyecto: Django para apps complejas, Flask para APIs simples.
¿Cómo actualizo Python sin romper la app?
Crea un nuevo venv con la nueva versión, instala dependencias, prueba. Si funciona, actualiza el servicio systemd para usar el nuevo venv.
Nuestra recomendación
Setup básico:
- Python con venv
- Gunicorn como WSGI server
- Nginx como proxy reverso
- Systemd para gestión de servicios
Para producción:
- Variables en .env
- Logs configurados
- Collectstatic automatizado
- Celery para tareas async
¿Necesitas hosting Python gestionado? La administración gestionada de Avantys incluye configuración y mantenimiento de aplicaciones Python.
Conclusión
Python en producción requiere una stack completa: virtualenv, Gunicorn, Nginx y systemd. Una vez configurado, tienes un entorno robusto y escalable.
Empieza con la configuración básica y añade Celery, caché Redis y monitorización según crezca tu proyecto.
¿Necesitas un VPS para Python? Explora los VPS de Avantys con Python preconfigurado.
¿Quieres que lo hagamos por ti?
En Avantys gestionamos tu web, hosting y crecimiento digital de punta a punta. Tú a lo importante.