vps-deploy

Flujo de despliegue

Convenciones & Deploy

  • Estructura: /srv/projects/<slug>/ con venv/, .env, run.py, deploy/.
  • Slugs: dominio-subdominio. Puertos apps 8101–8199; infra ≥ 9000.
  • run.py autodetecta: wsgi:app (gunicorn), main:app (uvicorn/FastAPI) o app:app (Flask). Fallback expone /health.
  • systemd: /etc/systemd/system/<slug>.service, ExecStart usa venv/bin/python run.py.
  • Caddy: vhosts con snippet (tls_cf) (DNS-01 Cloudflare) y health.
  • Monitor: infra.illanes00.cl + /status.json.

Runbook de deploy rápido

# crear proyecto
sudo mkdir -p /srv/projects/<slug> && cd /srv/projects/<slug>
python3 -m venv venv
echo PORT=<puerto> > .env
venv/bin/pip install --upgrade pip gunicorn uvicorn fastapi flask python-dotenv

# run.py estándar (módulos → respeta venv)
cat > run.py <<'PY'
#!/usr/bin/env python3
import os, sys, importlib, subprocess
from pathlib import Path
try:
  from dotenv import load_dotenv; load_dotenv(".env")
except Exception:
  pass
HOST=os.getenv("HOST","127.0.0.1"); PORT=os.getenv("PORT","8101")
exists=lambda p: Path(p).exists()
if exists("wsgi.py"):
  sys.exit(subprocess.call([sys.executable,"-m","gunicorn",f"--bind={HOST}:{PORT}","wsgi:app"]))
if exists("app/main.py") or exists("main.py"):
  target="app.main:app" if exists("app/main.py") else "main:app"
  try:
    importlib.import_module(target.split(":")[0])
    sys.exit(subprocess.call([sys.executable,"-m","uvicorn",target,"--host",HOST,"--port",PORT]))
  except Exception: pass
if exists("app.py"):
  try:
    appmod=importlib.import_module("app")
    if getattr(appmod,"app",None) is not None:
      sys.exit(subprocess.call([sys.executable,"-m","gunicorn",f"--bind={HOST}:{PORT}","app:app"]))
  except Exception: pass
from flask import Flask
app=Flask(__name__)
@app.get("/health")
def h(): return {"ok":True}
if __name__=="__main__": app.run(host=HOST, port=int(PORT))
PY
chmod +x run.py

# systemd
sudo tee /etc/systemd/system/<slug>.service >/dev/null <<'UNIT'
[Unit]
Description=%i service (run.py)
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=%i
WorkingDirectory=/srv/projects/%i
EnvironmentFile=-/srv/projects/%i/.env
ExecStart=/srv/projects/%i/venv/bin/python /srv/projects/%i/run.py
Restart=on-failure
StartLimitIntervalSec=120
StartLimitBurst=10
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full

[Install]
WantedBy=multi-user.target
UNIT

sudo systemctl daemon-reload
sudo systemctl enable --now <slug>

# Caddy vhost (ejemplo)
sudo tee /etc/caddy/sites.d/<slug>.caddy >/dev/null <<'CADDY'
dominio.example.com {
  import tls_cf
  encode zstd gzip
  @health path /health
  handle_path /health* {
    reverse_proxy 127.0.0.1:8101
  }
  reverse_proxy 127.0.0.1:8101
}
CADDY
sudo caddy reload --config /etc/caddy/Caddyfile

Cronograma recomendado

Cronograma

Hoy (prioridad máxima)

  1. Unificar Espacio Público: combinar ep-seguridad + illanes00-ep en una sola plataforma.
    • Checklist: consolidar modelos/ETL, rutas y frontend; dejar /seguridad como sección.
    • HTTP: mantener /health; smoke tests local→público; redirigir host viejo si aplica.

Próximo

  • Admin (read-only): panel consumiendo API central; luego proteger.
  • API central: catálogo de endpoints (+indexado OpenAPI) y límites con Redis.
  • PgBouncer: pool de conexiones; roles por app en PostgreSQL.
  • Mapa del sitio: autogenerado desde /etc/infra/sites.yaml.
  • Estilo unificado (tokens CSS mínimos).

Observabilidad mínima

  • Alertas: 5xx > 1% (5m), service down, p95 > umbral.
  • Dashboards: tráfico, latencia, errores, systemd restarts.

Notas de implementación

  • Despliegue vía run.py autodetect (gunicorn/uvicorn) y unit systemd simple.
  • Caddy con (tls_cf), /health proxyeado y compresión.

Playbook de migraciones

Migración & Flip DNS

  1. Inventariar en /etc/infra/sites.yaml.
  2. Crear unit systemd + Caddy vhost. Exigir /health 200 local.
  3. TTL bajo (120s). Validar público con curl -sS --resolve ...
  4. Flip DNS en Cloudflare (A/AAAA a la IP nueva). Smoke tests y métricas.
  5. TTL normal. Monitorear errores 5xx/latencias en Grafana/Netdata.

Redirects (decomission & aliases)

# ejemplo: ep-seguridad → plataforma unificada
ep-seguridad.illanes00.cl {
  import tls_cf
  redir https://ep.illanes00.cl/seguridad{uri} permanent
}

Copiar y clonar entornos

Copiar TODOS los docs

Este botón carga cada documento, los concatena con separadores y los copia al portapapeles para pegarlos directo en el LLM.

Cargar & Copiar