Módulo 4: Deployment Automation
Flujo Staging → Producción con Approval Gates
Flujo Staging → Producción con Approval Gates
Descripción
Las dos cápsulas previas cubrieron pre-deploy: changelog (cápsula 02) y readiness validation (cápsula 03). Esta cápsula cubre el flujo de promoción de código desde staging hasta producción con approval gates apropiados. Es donde el pipeline pasa de "código probado" a "código sirviendo tráfico real".
La pregunta central: ¿qué partes del deployment son seguras de automatizar y cuáles requieren intervención humana? La respuesta no es uniforme — depende del riesgo de cada step. Vas a aprender a diseñar el flujo, dónde colocar approval gates, cómo automatizar staging completamente, y por qué producción casi siempre debe tener un click humano.
Al terminar, vas a tener un workflow que deploya automáticamente a staging tras merge, valida el ambiente staging, y espera aprobación humana antes de promocionar a producción.
El Modelo Mental
┌──────────────────────────────────────────────────────┐
│ AUTOMATIZACIÓN COMPLETA (sin humanos) │
│ ✅ Build, lint, tests, security scan │
│ ✅ Deploy a staging │
│ ✅ Smoke tests post-deploy en staging │
│ ✅ Métricas de health check de staging │
└──────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────┐
│ GATE HUMANO │
│ 🔒 Click "Approve" en GitHub │
│ Reviewer humano confirma: │
│ - Vio el changelog │
│ - Confirmó que staging está saludable │
│ - Asume responsabilidad por el deploy │
└──────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────┐
│ AUTOMATIZACIÓN COMPLETA (post-approval) │
│ ✅ Deploy a producción │
│ ✅ Smoke tests post-deploy en producción │
│ ✅ Notificación al equipo │
│ ✅ Monitoring activo (con rollback automático │
│ que cubre cápsula del Módulo 5) │
└──────────────────────────────────────────────────────┘
El humano es la única gate. Todo lo demás es automático. La gate humana es rápida (un click) pero explícita — alguien asume responsabilidad por el deploy.
Por Qué Producción Necesita Humano
SIN GATE HUMANO:
→ Bug pasa todos los tests automatizados
→ Pasa staging (porque staging tiene menos tráfico/data)
→ Llega a producción
→ Empieza a fallar con tráfico real
→ Rollback automático corrige (si está configurado)
→ Pero los usuarios ya vieron errores
→ Damage limited but not zero
CON GATE HUMANO:
→ Mismo path hasta staging
→ Antes de producción, humano revisa:
- ¿Cambios riesgosos?
- ¿Métricas de staging saludables?
- ¿Es buen momento para deployar? (no fin de semana, no
antes de un evento crítico, etc.)
→ Si todo OK: click → producción
→ Si algo no convence: pausar, investigar, decidir
→ Damage previo a deploy
El humano agrega lo que la automatización no puede dar: contexto de timing, juicio sobre riesgo, autoridad para asumir responsabilidad.
Configurar Environments en GitHub
GitHub Environments es la primitiva clave para approval gates.
Setup paso a paso
Settings → Environments → New environment → "production"
Configuración importante:
- Required reviewers — agregar 1+ personas
- Wait timer — opcional (ej. 5 minutos para "cooling off")
- Deployment branches — restringir a
mainsolamente - Environment secrets — secrets exclusivos de prod
production
├── Required reviewers: [tech_lead, sre_lead]
├── Wait timer: 0 minutes
├── Deployment branches: main
├── Variables:
│ └── DEPLOYMENT_TARGET: prod-cluster
└── Secrets:
├── PROD_DATABASE_URL: xxx
└── PROD_STRIPE_KEY: xxx
Setup paralelo: staging
staging
├── Required reviewers: (none — automático)
├── Deployment branches: main
├── Variables:
│ └── DEPLOYMENT_TARGET: staging-cluster
└── Secrets:
├── STAGING_DATABASE_URL: xxx
└── STAGING_STRIPE_KEY: xxx (test key)
Diferencia clave: staging sin required reviewers (deploy automático), production con reviewers (gate humano).
El Workflow Completo
# .github/workflows/staged-deployment.yml
name: Staged Deployment
on:
push:
branches: [main]
permissions:
contents: read
deployments: write
concurrency:
group: deploy-${{ github.ref }}
cancel-in-progress: false # NO cancelar deploys en progreso
jobs:
# ============================================
# STAGE 1: Validación pre-deploy
# ============================================
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: { python-version: '3.11' }
- run: pip install anthropic requests
- name: Readiness validation
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_REPOSITORY: ${{ github.repository }}
run: python scripts/validate_readiness.py
- uses: actions/upload-artifact@v4
with:
name: readiness-report
path: readiness_report.json
# ============================================
# STAGE 2: Deploy a staging (automático)
# ============================================
deploy-staging:
needs: validate
runs-on: ubuntu-latest
environment: staging # ← env staging, sin required reviewers
outputs:
deploy_url: ${{ steps.deploy.outputs.url }}
steps:
- uses: actions/checkout@v4
- name: Deploy to staging
id: deploy
run: |
# Reemplaza este script con tu mecanismo real de deploy
# (kubectl, terraform, deploy script propio, etc.)
./scripts/deploy.sh staging
echo "url=https://staging.example.com" >> $GITHUB_OUTPUT
- name: Smoke tests on staging
run: ./scripts/smoke_tests.sh https://staging.example.com
- name: Validate staging metrics
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: python scripts/validate_staging.py
# ============================================
# STAGE 3: Deploy a producción (con approval gate)
# ============================================
deploy-production:
needs: deploy-staging
runs-on: ubuntu-latest
environment: production # ← env production con required reviewers
steps:
- uses: actions/checkout@v4
- name: Deploy to production
run: ./scripts/deploy.sh production
- name: Smoke tests on production
run: ./scripts/smoke_tests.sh https://app.example.com
- name: Notify team
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
run: python scripts/post_deploy_notification.py
El flujo:
- Validación corre primero (readiness check)
- Deploy a staging es automático tras validación exitosa
- Smoke tests + validación de métricas en staging
- Deploy a producción espera approval humano (porque environment requiere reviewers)
- Después del approval, deploy automático + smoke tests + notificación
El "click" del approval
Cuando el job deploy-production espera aprobación, GitHub muestra:
⏸️ Waiting for approval to deploy
Environment: production
Reviewers needed: 1 of 2
[Approve and deploy] [Reject]
Cualquier reviewer configurado puede aprobar. Click → deploy continúa.
Validación Automática de Staging
Antes de que el humano apruebe producción, el sistema debería darle información clara sobre el estado de staging. Acá Claude Code ayuda:
"""scripts/validate_staging.py
Análisis post-deploy de staging para informar la decisión de promoción.
"""
import json
import os
import subprocess
import sys
import requests
from anthropic import Anthropic
def get_staging_metrics() -> dict:
"""Obtener métricas de health de staging."""
# Adapt to tu observability stack
metrics = {
"error_rate": fetch_metric("error_rate", env="staging"),
"latency_p95": fetch_metric("latency_p95", env="staging"),
"throughput": fetch_metric("throughput", env="staging"),
"deployment_success": True, # del step previo
}
return metrics
def fetch_metric(name: str, env: str) -> float | None:
"""Wrapper de tu sistema de monitoring."""
# Ejemplo: Prometheus, Datadog, CloudWatch
# Implementación específica del stack
return None
def smoke_test_results() -> dict:
"""Leer resultados de smoke tests."""
try:
with open("smoke_results.json") as f:
return json.load(f)
except FileNotFoundError:
return {"passed": 0, "failed": 0, "skipped": 0}
def analyze_with_claude(metrics: dict, smoke: dict, changelog: str) -> str:
"""Análisis: ¿es seguro promocionar a producción?"""
prompt = f"""Analiza si es seguro promocionar este deploy de staging a producción.
CHANGELOG (qué se está deployando):
{changelog}
MÉTRICAS DE STAGING (post-deploy):
{json.dumps(metrics, indent=2)}
SMOKE TESTS:
{json.dumps(smoke, indent=2)}
Evalúa:
1. ¿Las métricas de staging son saludables?
2. ¿Los smoke tests pasaron?
3. ¿Hay señales en el changelog que requieran extra cuidado en prod?
4. ¿Recomiendas promocionar a producción ahora?
Devuelve un JSON:
{{
"recommendation": "promote|hold|investigate",
"summary": "1-2 párrafos con tu razonamiento",
"concerns": ["concern 1", "concern 2"] o [],
"checklist_for_human": ["verificar X antes de aprobar", "..."]
}}
"""
client = Anthropic()
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=2000,
messages=[{"role": "user", "content": prompt}],
)
text = response.content[0].text.strip()
if text.startswith("```"):
text = "\n".join(text.split("\n")[1:-1])
return json.loads(text)
def post_summary_to_pr(analysis: dict, repo: str, token: str):
"""Postear el análisis al PR para que el reviewer lo vea antes de aprobar."""
# Encontrar el PR asociado al merge commit
sha = os.environ["GITHUB_SHA"]
url = f"https://api.github.com/repos/{repo}/commits/{sha}/pulls"
headers = {"Authorization": f"Bearer {token}", "Accept": "application/vnd.github+json"}
r = requests.get(url, headers=headers)
if not r.ok or not r.json():
return
pr_number = r.json()[0]["number"]
body = format_summary_markdown(analysis)
comment_url = f"https://api.github.com/repos/{repo}/issues/{pr_number}/comments"
requests.post(comment_url, headers=headers, json={"body": body})
def format_summary_markdown(analysis: dict) -> str:
rec_emoji = {"promote": "✅", "hold": "⏸️", "investigate": "🚨"}
body = f"""# 🤖 Pre-Production Promotion Analysis
## Recomendación
{rec_emoji.get(analysis['recommendation'], '💬')} **{analysis['recommendation'].upper()}**
## Resumen
{analysis['summary']}
"""
if analysis.get("concerns"):
body += "## Concerns\n"
for c in analysis["concerns"]:
body += f"- ⚠️ {c}\n"
body += "\n"
if analysis.get("checklist_for_human"):
body += "## Antes de aprobar producción, verifica:\n"
for c in analysis["checklist_for_human"]:
body += f"- [ ] {c}\n"
return body
def main() -> int:
metrics = get_staging_metrics()
smoke = smoke_test_results()
# Leer changelog del último release
changelog_files = list(Path(".").glob("CHANGELOG_v*.md"))
changelog = changelog_files[0].read_text() if changelog_files else "No changelog found"
analysis = analyze_with_claude(metrics, smoke, changelog)
# Postear al PR (informativo, no bloqueante)
repo = os.environ.get("GITHUB_REPOSITORY")
token = os.environ.get("GITHUB_TOKEN")
if repo and token:
post_summary_to_pr(analysis, repo, token)
# Imprimir en logs
print(json.dumps(analysis, indent=2))
# Si la recomendación es investigate, fallar el job (no promocionar)
if analysis["recommendation"] == "investigate":
print("Recomendación: investigar. Pipeline pausado.")
return 1
return 0
if __name__ == "__main__":
from pathlib import Path
sys.exit(main())
Resultado: cuando el humano va a aprobar el deploy a producción, ya tiene en el PR un comment con análisis del estado de staging y un checklist específico de qué verificar.
Patrón Avanzado: Wait Timer
Para cambios de bajo riesgo, puedes agregar un "cooling off period" en lugar de approval explícito:
production environment
├── Required reviewers: 0
├── Wait timer: 30 minutes
├── Deployment branches: main
Resultado: el deploy a producción espera 30 minutos automáticamente. Si en ese tiempo no hay alerts ni cancelación manual, continúa. Si alguien detecta algo en staging, puede cancelar.
Cuándo usar:
- Equipos chicos sin SRE 24/7
- Deploys frecuentes de bajo riesgo
- Compañías donde la velocidad de iteración importa más que el approval explícito
Cuándo NO usar:
- Producción con tráfico crítico
- Deploys de cambios riesgosos (DB migrations, breaking changes)
- Cumplimiento regulatorio que requiere approval explícito
Patrón: Canary Deploy
Más sofisticado: en lugar de promover 100% del tráfico a producción de una vez, promover gradualmente:
deploy-canary:
needs: deploy-staging
environment: production-canary # ← 5% del tráfico
steps:
- run: ./scripts/deploy.sh canary
deploy-full:
needs: deploy-canary
environment: production-full # ← 100% del tráfico
# required reviewers acá
steps:
- run: ./scripts/deploy.sh full
El canary recibe 5% del tráfico durante un tiempo. Si las métricas son buenas, se promueve a 100%. Si no, rollback automático afecta solo al 5%.
Trade-off: más complejidad de infraestructura vs menos blast radius en incidents.
Trampas Comunes
Error 1: Approval gate sin contexto
Síntoma: El reviewer recibe la notificación de "approve production deploy" sin info. Aprueba a ciegas.
Por qué pasa: El approval gate no incluye el análisis previo.
Cómo corregir: Postear al PR el análisis de staging antes del approval (script validate_staging.py arriba). El reviewer tiene el contexto en su feed.
Error 2: Wait timer sin alertas activas
Síntoma: Configuraste 30 min de wait timer, pero el equipo no ve métricas en ese tiempo. El deploy sale a prod con un bug.
Por qué pasa: Wait timer asume que alguien está monitoreando. Si nadie mira, no aporta.
Cómo corregir: Configurar alerts apropiadas en staging que notifiquen al equipo. Wait timer + alerts activas = combo.
Error 3: Mismo set de secrets para staging y prod
Síntoma: Un bug en staging usa una API key de producción y afecta sistemas reales.
Por qué pasa: Por conveniencia, el mismo STRIPE_KEY se usa en ambos.
Cómo corregir: Siempre secrets separados por environment. Stripe tiene test keys vs live keys — úsalas. Mismo principio para todo lo demás.
Error 4: Auto-rollback no configurado
Síntoma: Deploy a producción falla, equipo improvisando rollback bajo presión.
Por qué pasa: Auto-rollback es trabajo separado que dejaron "para después".
Cómo corregir: Configurar auto-rollback antes de habilitar deploys automáticos a producción. Es lo que cubre la cápsula del Módulo 5.
Error 5: cancel-in-progress: true en deploys
Síntoma: Mergeas dos PRs seguidos. El segundo cancela el deploy del primero a mitad. Estado inconsistente.
Por qué pasa: cancel-in-progress: true está bien para CI checks, mal para deploys.
Cómo corregir: Para deploys, siempre cancel-in-progress: false. Los deploys deben completar serialmente.
Diagnóstico
Pregunta 1: ¿Tu deploy a producción tiene approval gate explícito?
Si no, dependes 100% de automatización. Para sistemas críticos, el humano agrega valor.
Pregunta 2: ¿El reviewer recibe contexto antes de aprobar?
Si solo ve "approve deploy", aprueba a ciegas. Análisis post-staging + checklist le da el contexto.
Pregunta 3: ¿Tus secrets están separados entre staging y producción?
Si son los mismos, un bug en staging puede tocar producción real.
Pregunta 4: ¿Tu workflow tiene `concurrency: cancel-in-progress: false` para deploys?
Si está en true, deploys consecutivos se cancelan a mitad. Mal patrón.
Pregunta 5: ¿Tienes rollback automático configurado, o el rollback es manual?
Manual = bajo presión, propenso a errores. Automático con triggers apropiados es lo robusto. Cápsula del Módulo 5 lo desarrolla.
Ejercicios
Ejercicio 1: Configurar environments (Fácil)
Crea los environments staging y production en GitHub Settings:
- staging: sin reviewers, deployment branches main
- production: con reviewers (al menos tú mismo en testing), deployment branches main
Configura secrets de prueba distintos en cada uno.
Ejercicio 2: Pipeline de 3 stages (Medio)
Implementa el workflow con los 3 jobs (validate, deploy-staging, deploy-production). Verifica que:
- validate corre primero
- deploy-staging corre tras validate exitoso, sin approval
- deploy-production espera approval antes de correr
Usa scripts mock (echo "Deploying to ...") para testear el flujo sin infraestructura real.
Ejercicio 3: Análisis pre-promoción con Claude (Difícil)
Implementa validate_staging.py que:
- Obtiene métricas mock de staging
- Pasa el changelog + métricas a Claude
- Genera recomendación con concerns y checklist
- Postea el análisis como comment en el PR
- Falla el job si la recomendación es "investigate"
Resumen
- El humano es la única gate entre staging y producción para cambios críticos
- GitHub Environments es la primitiva: required reviewers, wait timers, environment secrets
- Análisis automático pre-approval da contexto al humano (recomendación, concerns, checklist)
- Secrets separados por environment evita cross-contamination
cancel-in-progress: falsepara deploys (a diferencia de CI checks)- Wait timer es alternativa para low-risk deploys, pero solo con monitoring activo
- Canary deploys reducen blast radius en cambios riesgosos
Próxima cápsula: 05 — Proyecto: Workflow de deployment completo. Última cápsula técnica del módulo 4. Combinas changelog + readiness + staged deployment en un workflow end-to-end que vas a poder adaptar a tu proyecto.
Recursos Adicionales
- GitHub Environments — Setup completo
- GitHub: Required reviewers — Configuración de gates
- Canary Deployments — Martin Fowler sobre el patrón
- Blue-Green Deployments — Alternativa al canary
- 12-Factor App: Backing services — Por qué staging y prod deben ser idénticos
- SRE Workbook: Release Engineering — Capítulo del libro de Google