DCA · Runbook de despliegue (Hetzner)
DCA corre en un Hetzner CX23 (62.238.33.149, Ubuntu 26.04 LTS). La migración desde PythonAnywhere se cerró del todo (Fase 4, 2026-06-11): ya no queda nada de PA.
Stack de producción
| Componente | Versión | Nota |
|---|---|---|
| Python | 3.14.4 | sistema y venv (/home/dca/dca/venv) |
| Flask | 3.0.3 | pin en requirements.txt |
| PyMuPDF | 1.27.2.3 | wheel abi3 manylinux cp314 → instala sin compilar |
| whitenoise | 6.12.0 | fijado |
| gunicorn | 26.0.0 | systemd --workers 4 --timeout 120 |
Gunicorn (4 workers) tras Nginx (reverse proxy, sirve estáticos directamente, client_max_body_size 25M) tras Cloudflare (proxy + Let’s Encrypt vía certbot-dns-cloudflare). En Cloudflare hay una Cache Rule (Bypass) para sw.js / js_modules/* / css/* → los deploys de código propagan solos.
Auto-deploy (lo normal)
Cada push a main (salvo docs-only) despliega solo en ~30 s vía un runner autoalojado de GitHub Actions en el propio Hetzner (~/actions-runner, etiqueta hetzner): conecta SALIENTE a GitHub (no hace falta abrir el firewall) y ejecuta deploy.sh localmente. Workflow: .github/workflows/deploy.yml.
- Disparo manual: pestaña Actions → Run workflow.
- Estado del runner:
gh api repos/Esquembri/domo-channel-assistance/actions/runners. - Arranque persistente: crontab de usuario (
@reboot+ watchdog*/5), sin root.
Deploy manual (si el runner falla)
ssh dca@62.238.33.149
bash /home/dca/dca/deploy/deploy.sh # git pull + pip install + systemctl restart dca
El usuario dca solo tiene NOPASSWD para systemctl restart dca (no tiene sudo general). Para ver permisos reales: sudo -n -l (NO sudo -n true, que da falso negativo).
CI
.github/workflows/ci.yml (separado del deploy): instala requirements.txt + requirements-dev.txt en Python 3.14 y corre pytest en cada push/PR a main → caza la deriva “pin sin wheel” antes del deploy.
Arquitectura de la app: [[concept—dca-tech—arquitectura]].
Véase también
- [[concept—dca-tech—overview]]
- [[concept—dca-tech—arquitectura]]