🎯 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ónCultura DevOps en 3 ideas:
- Automatiza lo repetitivo (tests, builds, despliegues). El humano se equivoca; la máquina no.
- Infraestructura como código: el servidor se describe en archivos versionados, no se configura a mano (así es reproducible).
- 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:
# 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.logyjournalctl -fde 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:
# 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):
- Usuario no-root +
sudo.- SSH solo con clave (desactiva contraseñas).
- Firewall cerrado salvo 22/80/443.
- Actualizaciones de seguridad automáticas (
unattended-upgrades).- 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.
# /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.targetsudo 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=alwayses tu red de seguridad: si tu app crashea a las 3 AM, systemd la levanta sola. Con Docker, el equivalente esrestart: unless-stoppeden 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:
# .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 servidor14.6 · Migraciones en producción (con cuidado)
Cuando despliegas cambios de esquema, hay que correr migraciones. Nunca a mano en la BD real.
# 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:
- Siempre haz backup de la BD antes de una migración.
- 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).
- 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.
# 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 tiendaRegla 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:
# crontab -e → backup diario a las 3 AM, sube a almacenamiento externo
0 3 * * * /home/deploy/scripts/backup.sh14.8 · Observabilidad (logs, métricas, alertas)
No puedes arreglar lo que no ves. Las 3 patas:
| Pata | Qué es | Herramientas 2026 |
|---|---|---|
| Logs | Registro de qué pasó | Loki + Grafana, ELK, o el logging de tu nube |
| Métricas | Números en el tiempo (CPU, req/s, latencia) | Prometheus + Grafana |
| Trazas | El viaje de una petición entre servicios | OpenTelemetry, Jaeger |
| Alertas | Avisos cuando algo va mal | Grafana Alerting, Sentry (errores) |
Lo mínimo para empezar:
- Logs estructurados (JSON) desde tu app, no
print()sueltos. - Sentry (o similar) para capturar errores/excepciones con contexto. Gratis para empezar y te avisa por email/Slack cuando algo peta en producción.
- 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
| Estrategia | Cómo funciona | Cuándo |
|---|---|---|
| Recreate | Para la vieja, arranca la nueva | Simple, hay unos segundos de corte |
| Rolling | Sustituye instancias de a poco | Sin corte, el estándar |
| Blue-Green | Dos entornos idénticos; cambias el tráfico de golpe | Rollback instantáneo |
| Canary | Envías a la nueva versión un % del tráfico primero | Detectar 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 → DevOpsLo 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.mdpara comandos rápidos y elglosario.mdcuando una palabra no te suene. Y anota tus descubrimientos ennotas.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.