Skip to content

🎯 Meta: llevar tu aplicación a un servidor real y mantenerla viva, segura y actualizada de forma automática. Aquí unes todo lo anterior: tu código dockerizado, detrás de Nginx, en un servidor Linux, desplegado con un git push. Esto es DevOps.


14.1 · ¿Qué es DevOps?

DevOps une Development (crear software) y Operations (operarlo en producción). La idea: automatizar todo el camino del código a producción para entregar rápido y sin romper.

Escribes código  →  git push  →  [CI: tests]  →  [CD: build + deploy]  →  producción
                                    ▲ automático                  ▲ automático
                              si falla, se detiene         despliegue sin intervención

Cultura DevOps en 3 ideas:

  1. Automatiza lo repetitivo (tests, builds, despliegues). El humano se equivoca; la máquina no.
  2. Infraestructura como código: el servidor se describe en archivos versionados, no se configura a mano (así es reproducible).
  3. Mide todo (logs, métricas, alertas): no puedes arreglar lo que no ves.

14.2 · Linux para backend (lo imprescindible)

Casi todos los servidores del mundo corren Linux. Comandos que debes dominar:

bash
# Navegación y archivos
ls -la          cd /ruta        pwd         cat archivo
cp / mv / rm    mkdir -p        find / grep         tail -f archivo.log

# Permisos (importante en servidores)
chmod 640 archivo        # permisos (dueño rw, grupo r, otros nada)
chown user:group archivo # cambiar dueño

# Procesos
ps aux | grep nginx      # ver procesos
top / htop               # monitor en vivo (CPU, RAM)
kill -9 <pid>            # matar un proceso

# Servicios (systemd)
systemctl status nginx   # estado
systemctl restart nginx  # reiniciar
systemctl enable nginx   # que arranque al bootear
journalctl -u nginx -f   # logs de un servicio en vivo

# Red
curl localhost:8000      # probar un endpoint
ss -tlnp                 # puertos abiertos
ping / netstat

💡 Tip: aprende tail -f archivo.log y journalctl -f de memoria. El 80% del "debugging en producción" es leer logs en vivo mientras reproduces el problema.


14.3 · Preparar un VPS desde cero

Un VPS (Virtual Private Server) es un servidor Linux en la nube por pocos €/mes (Hetzner, DigitalOcean, Vultr…). Pasos para dejarlo listo y seguro:

bash
# 1. Conéctate por SSH
ssh root@tu_ip

# 2. Actualiza el sistema
apt update && apt upgrade -y

# 3. Crea un usuario no-root (nunca trabajes como root)
adduser deploy
usermod -aG sudo deploy

# 4. Configura SSH con clave (no contraseña) — desde TU máquina:
ssh-keygen -t ed25519            # genera tu par de claves
ssh-copy-id deploy@tu_ip         # copia tu clave pública al servidor

# 5. Endurece SSH: /etc/ssh/sshd_config
#    PermitRootLogin no
#    PasswordAuthentication no    ← solo claves, adiós fuerza bruta
systemctl restart ssh

# 6. Firewall (UFW): solo abre lo necesario
ufw allow OpenSSH
ufw allow 80
ufw allow 443
ufw enable

# 7. Instala Docker
curl -fsSL https://get.docker.com | sh
usermod -aG docker deploy

⚠️ Lo mínimo de seguridad en un servidor (no negociable):

  1. Usuario no-root + sudo.
  2. SSH solo con clave (desactiva contraseñas).
  3. Firewall cerrado salvo 22/80/443.
  4. Actualizaciones de seguridad automáticas (unattended-upgrades).
  5. Fail2ban para banear IPs que intentan fuerza bruta.

🔗 El endurecimiento de servidores conecta directo con tu Manual de Ciberseguridad. Aquí lo construyes; allí entiendes cada ataque que estás mitigando.


14.4 · systemd — mantener tu app viva

Si no usas Docker, systemd gestiona tu app como un servicio: la arranca al bootear y la reinicia si se cae.

ini
# /etc/systemd/system/miapi.service
[Unit]
Description=Mi API FastAPI
After=network.target

[Service]
User=deploy
WorkingDirectory=/home/deploy/miapi
ExecStart=/home/deploy/miapi/.venv/bin/gunicorn main:app -w 4 -k uvicorn.workers.UvicornWorker -b 0.0.0.0:8000
Restart=always                 # si se cae, reinícialo
RestartSec=3
EnvironmentFile=/home/deploy/miapi/.env

[Install]
WantedBy=multi-user.target
bash
sudo systemctl daemon-reload
sudo systemctl enable --now miapi     # arrancar y habilitar al boot
sudo systemctl status miapi
sudo journalctl -u miapi -f           # ver sus logs

🧠 Restart=always es tu red de seguridad: si tu app crashea a las 3 AM, systemd la levanta sola. Con Docker, el equivalente es restart: unless-stopped en Compose (cap. 12).


14.5 · CI/CD con GitHub Actions

CI (Integración Continua) = en cada push, corre tests automáticamente. CD (Continuous Delivery/Deployment) = si los tests pasan, despliega automáticamente.

Un pipeline completo:

yaml
# .github/workflows/deploy.yml
name: CI/CD
on:
  push:
    branches: [main]

jobs:
  # ─── CI: tests ───
  test:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:18
        env: { POSTGRES_PASSWORD: test }
        ports: ['5432:5432']
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 24 }
      - run: npm ci
      - run: npm test          # 🔴 si falla, NO se despliega

  # ─── CD: build + deploy (solo si test pasó) ───
  deploy:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Build y push de la imagen
        run: |
          docker build -t ghcr.io/${{ github.repository }}:${{ github.sha }} .
          echo ${{ secrets.GHCR_TOKEN }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin
          docker push ghcr.io/${{ github.repository }}:${{ github.sha }}
      - name: Desplegar en el servidor por SSH
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: deploy
          key: ${{ secrets.SSH_KEY }}
          script: |
            cd /home/deploy/miapp
            docker compose pull
            docker compose up -d      # despliegue sin downtime

💡 Tip: los secrets (claves SSH, tokens, contraseñas) van en la configuración de secretos de GitHub (Settings → Secrets), nunca en el YAML. El pipeline los inyecta en ejecución. Igual que el .env: los secretos jamás en el repositorio.

El flujo completo que acabas de construir:

git push a main
   → GitHub Actions corre los tests con PostgreSQL
   → si pasan: construye la imagen Docker y la sube al registry
   → se conecta por SSH al servidor
   → el servidor baja la nueva imagen y reinicia el contenedor
   → tu cambio está en producción, sin que tocaras el servidor

14.6 · Migraciones en producción (con cuidado)

Cuando despliegas cambios de esquema, hay que correr migraciones. Nunca a mano en la BD real.

bash
# En el pipeline o al desplegar, antes de arrancar la nueva versión:
docker compose run --rm api php artisan migrate --force     # Laravel
docker compose run --rm api alembic upgrade head            # FastAPI/Flask
docker compose run --rm api npx prisma migrate deploy       # NestJS

⚠️ Reglas de migraciones en producción:

  1. Siempre haz backup de la BD antes de una migración.
  2. Migraciones compatibles hacia atrás: añade columnas nullable primero, luego rellena, luego haz obligatorio. Nunca borres una columna que la versión anterior aún usa (rompe durante el despliegue).
  3. Prueba la migración en un entorno de staging idéntico a producción antes.

14.7 · Backups (lo que te salva la vida)

🔥 La pregunta que define si eres profesional: "Si la BD se corrompe ahora mismo, ¿cuántos datos pierdo?" Si no sabes la respuesta, no tienes backups de verdad.

bash
# Backup de PostgreSQL (en un cron diario)
docker exec pg pg_dump -U postgres tienda | gzip > backup_$(date +%F).sql.gz

# Restaurar
gunzip -c backup_2026-07-09.sql.gz | docker exec -i pg psql -U postgres tienda

Regla 3-2-1 de backups: 3 copias, en 2 medios distintos, 1 fuera del sitio (otra nube/región). Y —crucial— prueba restaurar de vez en cuando: un backup que nunca restauraste podría estar corrupto y no lo sabrías.

Automatízalo con cron:

bash
# crontab -e  →  backup diario a las 3 AM, sube a almacenamiento externo
0 3 * * * /home/deploy/scripts/backup.sh

14.8 · Observabilidad (logs, métricas, alertas)

No puedes arreglar lo que no ves. Las 3 patas:

PataQué esHerramientas 2026
LogsRegistro de qué pasóLoki + Grafana, ELK, o el logging de tu nube
MétricasNúmeros en el tiempo (CPU, req/s, latencia)Prometheus + Grafana
TrazasEl viaje de una petición entre serviciosOpenTelemetry, Jaeger
AlertasAvisos cuando algo va malGrafana Alerting, Sentry (errores)

Lo mínimo para empezar:

  1. Logs estructurados (JSON) desde tu app, no print() sueltos.
  2. Sentry (o similar) para capturar errores/excepciones con contexto. Gratis para empezar y te avisa por email/Slack cuando algo peta en producción.
  3. Un healthcheck (GET /salud → 200) que un servicio externo (UptimeRobot) monitorea y te avisa si tu app cae.

💡 Tip: el primer día en producción, añade Sentry y un endpoint /salud. Con eso ya sabes cuándo tu app falla antes que tus usuarios te lo digan. Es la diferencia entre enterarte por un dashboard o por un cliente enfadado.


14.9 · Estrategias de despliegue sin downtime

EstrategiaCómo funcionaCuándo
RecreatePara la vieja, arranca la nuevaSimple, hay unos segundos de corte
RollingSustituye instancias de a pocoSin corte, el estándar
Blue-GreenDos entornos idénticos; cambias el tráfico de golpeRollback instantáneo
CanaryEnvías a la nueva versión un % del tráfico primeroDetectar problemas con pocos usuarios

Para un proyecto normal con Docker Compose + Nginx, un rolling simple basta: levantas la nueva imagen, Nginx sigue sirviendo, y cuando la nueva está sana, se corta la vieja. Herramientas como Dokku, Coolify o Kamal automatizan esto sobre un VPS sin complejidad de Kubernetes.

💡 Coolify / Dokku (2026): son "tu propio Heroku" en tu VPS. Conectas tu repo, y cada push despliega solo, con HTTPS automático y rollback. Ideal si quieres CD moderno sin montar todo el pipeline a mano. Muy recomendado para empezar.


14.10 · Checklist de "listo para producción"

Antes de decir "está en producción", repasa:

Seguridad
  ☐ HTTPS forzado, certificado válido y auto-renovable
  ☐ Firewall cerrado (solo 22/80/443), SSH con clave, fail2ban
  ☐ Secretos en variables de entorno / gestor de secretos (NO en git ni en la imagen)
  ☐ Cabeceras de seguridad en Nginx
  ☐ Usuario no-root en contenedores y en el servidor

Fiabilidad
  ☐ Restart automático (systemd / Docker restart policy)
  ☐ Healthcheck y monitor externo (te avisa si cae)
  ☐ Backups automáticos Y restauración probada
  ☐ Logs centralizados + captura de errores (Sentry)

Proceso
  ☐ CI corre tests en cada push (nada llega roto)
  ☐ CD despliega automático tras tests verdes
  ☐ Migraciones versionadas, compatibles hacia atrás, con backup previo
  ☐ Variable de entorno APP_ENV=production (debug OFF)

Rendimiento
  ☐ Nginx con gzip + caché de estáticos + rate limiting
  ☐ Índices en la BD para las consultas frecuentes
  ☐ Imágenes Docker pequeñas (multi-stage)

✅ Ejercicio final del libro

Lleva una de tus APIs del blog a producción de verdad, de punta a punta:

1. Contrata un VPS barato y endurécelo (usuario no-root, SSH por clave, UFW,
   fail2ban, Docker).
2. Sube tu app dockerizada (cap. 12) con PostgreSQL y Nginx (cap. 13).
3. Consigue un dominio y HTTPS con Certbot.
4. Monta el pipeline CI/CD en GitHub Actions: tests → build → deploy por SSH.
5. Configura migraciones automáticas en el despliegue (con backup previo).
6. Añade healthcheck + Sentry + un monitor externo que te avise si cae.
7. Programa backups diarios automáticos y PRUEBA restaurar uno.
8. Haz un cambio pequeño, git push, y observa cómo llega solo a producción.
9. Repasa el checklist de arriba y tacha todo.

Si completas esto, has cerrado el círculo entero: de una idea a una aplicación en producción, segura, testeada y desplegada automáticamente. Eso es ser un desarrollador backend / DevOps completo. 🎉

💡 Pistas de la solución
  • Endurece el VPS en este orden: crea el usuario no-root y prueba que puedes entrar por SSH con él ANTES de deshabilitar el login de root — si lo haces al revés y algo falla, te quedas fuera del servidor.
  • Para probar la restauración de backup del paso 7: no restaures sobre la misma base de datos. Crea una BD nueva vacía, restaura ahí, y compara el conteo de filas de una tabla clave con la original — eso demuestra que el backup es realmente usable, no solo que "el archivo existe".
  • El pipeline CI/CD del paso 4 debe fallar visiblemente si los tests fallan — verifica esto a propósito: rompe un test, haz push, y confirma que el deploy NO se dispara.

🧠 Autoevaluación

Son categorías de riesgo distintas que fallan de formas distintas: un fallo de seguridad expone datos, uno de fiabilidad tumba el servicio, uno de proceso deja pasar código roto, uno de rendimiento degrada la experiencia bajo carga. Separarlas ayuda a priorizar — normalmente seguridad y fiabilidad van antes que rendimiento en un lanzamiento inicial.

Un backup puede estar corrupto, incompleto, o simplemente el proceso de restauración puede fallar por una razón que solo descubres al intentarlo. Sin haber restaurado al menos una vez en un entorno de prueba, un backup es una esperanza, no una garantía — el día que lo necesites de verdad no es el momento de descubrir que no funciona.

Recreate para la versión vieja antes de arrancar la nueva, causando un corte de servicio real aunque sea breve. Rolling sustituye instancias progresivamente: la app sigue respondiendo con las instancias antiguas mientras las nuevas arrancan y pasan su readiness check, logrando cero downtime perceptible para el usuario.

Son mitigaciones para problemas distintos. El backup te permite volver al estado anterior si algo sale mal. Pero durante un rolling deploy, instancias viejas y nuevas del código conviven brevemente contra la MISMA base de datos — si la migración rompe la compatibilidad con el código antiguo, esas instancias viejas fallan antes de que termine el despliegue, aunque tengas backup.


🎓 Palabras finales

Recorriste el camino entero:

Fundamentos → SQL → 6 lenguajes/frameworks → Testing → SOLID → Arquitectura → Docker → Nginx → DevOps

Lo más importante que te llevas no es la sintaxis de ningún framework (esos cambian). Son los conceptos que reconocerás en cualquier tecnología nueva: petición/respuesta, CRUD, validación, capas, inyección de dependencias, tests, contenedores, despliegue.

El siguiente paso es tuyo: elige un proyecto que te importe y constrúyelo de principio a fin con este libro al lado. Se aprende construyendo, no leyendo. Vuelve a los capítulos cuando lo necesites; ese es el propósito de este manual.

Consulta la cheatsheet.md para comandos rápidos y el glosario.md cuando una palabra no te suene. Y anota tus descubrimientos en notas.md.

¡A construir! 🚀


Siguiente: 15-fundamentos-frontend.md — la Parte V te lleva al otro lado del cable: HTML, CSS, JavaScript, HTMX, React, Next.js y Vue.