Módulo 6: Proyecto — Pipeline CI/CD Completo con Claude Code
Implementación: Stages 4-6 (Deployment)
Implementación: Stages 4-6 (Deployment)
Descripción
Esta cápsula construye la mitad post-merge del pipeline: desde merge a main hasta producción servida a usuarios. Implementas changelog automático, readiness validation, deploy a staging con validación, approval gate humano, y deploy a producción con notificación al equipo.
Es la fase de mayor riesgo (los cambios afectan infraestructura real) y por lo tanto la que más disciplina requiere. Cada step tiene un check antes de avanzar al siguiente. La gate humana antes de producción es la única intervención manual del flujo automático.
Al terminar, vas a tener Phases 2-3 funcionando: el código mergeado a main llega automáticamente a staging, y con un click humano, a producción.
La Estructura del Workflow
# .github/workflows/staged-deployment.yml
name: Staged Deployment (Phases 2-3)
on:
push:
branches: [main]
permissions:
contents: write # para tags
pull-requests: write # para comments
deployments: write # para registrar deployments
issues: write # para crear changelog issue si aplica
concurrency:
group: deploy-${{ github.ref }}
cancel-in-progress: false # NUNCA cancelar deploys
env:
PYTHON_VERSION: '3.11'
jobs:
# ============================================
# PHASE 2: Pre-Deployment
# ============================================
generate-changelog:
runs-on: ubuntu-latest
outputs:
changelog_file: ${{ steps.gen.outputs.file }}
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- uses: actions/setup-python@v5
with: { python-version: '3.11', cache: 'pip' }
- run: pip install anthropic requests
- name: Generate changelog
id: gen
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_REPOSITORY: ${{ github.repository }}
NEW_TAG: ${{ github.sha }}
run: |
python scripts/generate_changelog.py
FILE=$(ls CHANGELOG_*.md | head -1)
echo "file=$FILE" >> $GITHUB_OUTPUT
- uses: actions/upload-artifact@v4
with:
name: changelog
path: CHANGELOG_*.md
retention-days: 30
readiness-validation:
runs-on: ubuntu-latest
outputs:
status: ${{ steps.validate.outputs.status }}
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- uses: actions/setup-python@v5
with: { python-version: '3.11', cache: 'pip' }
- run: pip install anthropic requests
- name: Run readiness validation
id: validate
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_REPOSITORY: ${{ github.repository }}
TARGET_ENV: production
run: |
python scripts/validate_readiness.py
if [ $? -eq 0 ]; then
echo "status=ready" >> $GITHUB_OUTPUT
else
echo "status=blocked" >> $GITHUB_OUTPUT
exit 1
fi
- uses: actions/upload-artifact@v4
if: always()
with:
name: readiness-report
path: readiness_report.json
# ============================================
# PHASE 3: Deployment
# ============================================
deploy-staging:
needs: [generate-changelog, readiness-validation]
runs-on: ubuntu-latest
environment: staging
outputs:
previous_release: ${{ steps.capture.outputs.previous }}
new_release: ${{ steps.deploy.outputs.new }}
deploy_url: ${{ steps.deploy.outputs.url }}
steps:
- uses: actions/checkout@v4
- name: Capture previous release
id: capture
run: |
PREV=$(./scripts/get_current_release.sh staging)
echo "previous=$PREV" >> $GITHUB_OUTPUT
- name: Deploy to staging
id: deploy
env:
DATABASE_URL: ${{ secrets.STAGING_DATABASE_URL }}
run: |
NEW=$(./scripts/deploy.sh staging)
echo "new=$NEW" >> $GITHUB_OUTPUT
echo "url=https://staging.example.com" >> $GITHUB_OUTPUT
- name: Smoke tests on staging
run: ./scripts/smoke_tests.sh https://staging.example.com
validate-staging:
needs: deploy-staging
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: { python-version: '3.11', cache: 'pip' }
- run: pip install anthropic requests
- uses: actions/download-artifact@v4
with: { name: changelog }
- name: Validate staging with Claude
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_REPOSITORY: ${{ github.repository }}
METRICS_API_URL: ${{ secrets.METRICS_API_URL }}
METRICS_API_TOKEN: ${{ secrets.METRICS_API_TOKEN }}
run: python scripts/validate_staging.py
deploy-production:
needs: validate-staging
runs-on: ubuntu-latest
environment: production # ← required reviewers
outputs:
previous_release: ${{ steps.capture.outputs.previous }}
new_release: ${{ steps.deploy.outputs.new }}
steps:
- uses: actions/checkout@v4
- name: Capture previous release
id: capture
run: |
PREV=$(./scripts/get_current_release.sh production)
echo "previous=$PREV" >> $GITHUB_OUTPUT
- name: Deploy to production
id: deploy
env:
DATABASE_URL: ${{ secrets.PROD_DATABASE_URL }}
run: |
NEW=$(./scripts/deploy.sh production)
echo "new=$NEW" >> $GITHUB_OUTPUT
- 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
Lo importante:
concurrency: cancel-in-progress: false— deploys nunca se cancelan a mitad- Phase 2 paralela (changelog + readiness en simultáneo)
- Phase 3 secuencial: staging → validate → approval → production
environment: productionagrega la gate humana- Capture de previous_release antes de deploy (para rollback en Phase 4)
Scripts Reusados
La mayoría de scripts vienen de módulos previos:
generate_changelog.py ← Módulo 4 cápsula 02
validate_readiness.py ← Módulo 4 cápsula 03
validate_staging.py ← Módulo 4 cápsula 04
post_deploy_notification.py ← Módulo 4 cápsula 05
deploy.sh ← Módulo 4 cápsula 05 (mock)
smoke_tests.sh ← Módulo 4 cápsula 05 (mock)
get_current_release.sh ← Módulo 5 cápsula 04
Punto importante: no reescribir nada. Reusar lo que ya construiste. Si en módulos previos los scripts están bien, este pipeline es ensamblaje, no construcción nueva.
El Script Faltante: get_current_release.sh
Este script es nuevo en este módulo. Adapta a tu plataforma:
#!/bin/bash
# scripts/get_current_release.sh
# Retorna el release actualmente deployado en un environment
set -euo pipefail
ENV="${1:?'Usage: get_current_release.sh <env>'}"
# Adapta según tu plataforma:
# Kubernetes
# CURRENT=$(kubectl get deployment app -n $ENV -o jsonpath='{.spec.template.metadata.labels.version}')
# AWS ECS
# CURRENT=$(aws ecs describe-services --cluster $ENV --services app \
# --query 'services[0].taskDefinition' --output text | rev | cut -d':' -f1 | rev)
# Heroku
# CURRENT=$(heroku releases --app "app-$ENV" --json | jq -r '.[0].version')
# Docker Compose / simple
# CURRENT=$(docker inspect app-$ENV --format '{{ index .Config.Labels "version" }}')
# Mock para demo (NO usar en producción real)
CURRENT="v$(date +%s)"
echo "$CURRENT"
Reemplaza la implementación mock con la lógica real de tu plataforma. Es el único lugar específico de tu infrastructure que tienes que adaptar.
Configurar los Environments en GitHub
Para que el pipeline funcione, configura los environments antes de pushear el workflow:
Environment: staging
Settings → Environments → New environment → "staging"
Configuración:
- Required reviewers: (vacío — automático)
- Wait timer: 0 minutes
- Deployment branches: main only
- Variables:
- STAGING_URL: https://staging.example.com
- Secrets:
- STAGING_DATABASE_URL: [valor]
- STAGING_API_TOKENS: [valores]
Environment: production
Settings → Environments → New environment → "production"
Configuración:
- Required reviewers: [tech_lead, sre_lead] ← CRÍTICO
- Wait timer: 0 minutes (o 5 si quieres cooling-off)
- Deployment branches: main only
- Variables:
- PROD_URL: https://app.example.com
- Secrets:
- PROD_DATABASE_URL: [valor diferente al de staging]
- PROD_API_TOKENS: [valores reales de prod]
Sin estas configuraciones, el workflow falla o (peor) deploya a producción sin gate humana.
Branch Protection para main
Settings → Branches → Branch protection rules → main
Configuración:
☑ Require a pull request before merging
☑ Require approvals: 1
☑ Dismiss stale pull request approvals when new commits are pushed
☑ Require review from Code Owners (opcional)
☑ Require status checks to pass before merging
☑ Require branches to be up to date before merging
Required status checks:
☑ tests-and-linting
☑ security-scan
☑ pr-review-summary
☑ Require conversation resolution before merging
☑ Restrict who can push to matching branches
- Solo bots de CI o emergency-team
Sin branch protection, alguien con write access puede pushear directo a main saltando todo el pipeline. Branch protection es la línea de defensa básica.
Flujo Completo: Lo Que Pasa al Mergear
T+0: Developer mergea PR → push a main
T+0: Workflow staged-deployment se dispara
T+0-3min: Phase 2 paralela:
- generate-changelog (~1 min)
- readiness-validation (~2 min)
T+3min: Si ambos pasaron, comienza Phase 3:
T+3-7min: deploy-staging
T+7-9min: smoke-tests-staging
T+9-10min: validate-staging (Claude analiza métricas + changelog)
T+10min: GitHub muestra "Waiting for approval to deploy: production"
Notificación a reviewers
T+10min - variable: Approval wait
- Reviewer ve el PR con análisis de staging
- Click "Approve"
- O click "Reject" + comment
T+approval+0: Comienza deploy-production
T+approval+4min: Deploy completo
T+approval+6min: Smoke tests en producción
T+approval+7min: Notification al equipo
TIEMPO TOTAL HAPPY PATH: ~17 min + approval wait
El Análisis Pre-Promoción
Cuando el reviewer humano va a aprobar, ya tiene el comment del job validate-staging en el PR. Eso le da contexto:
Lo que ve el reviewer ANTES de aprobar:
1. PR original con su descripción
2. Comments de Phase 1 (code review, security)
3. Métricas y status del workflow run
4. Comment del job validate-staging:
- Recomendación de Claude (promote/hold/investigate)
- Concerns identificados
- Checklist específico antes de aprobar
5. Botón "Approve and deploy"
El reviewer no aprueba a ciegas. Tiene contexto rich. La cápsula 04 del Módulo 4 detalla validate_staging.py.
Trampas Comunes
1. cancel-in-progress: true para deploys
Síntoma: Haces merge de 2 PRs seguidos. El segundo deploy cancela el primero a mitad. Estado inconsistente en producción.
Por qué pasa: Configuración heredada de Phase 1 (donde sí queremos cancelar).
Cómo corregir: cancel-in-progress: false para deploys siempre.
2. Olvidar capturar previous_release
Síntoma: Phase 4 (cápsula 05) necesita revertir, pero no sabes a qué versión.
Por qué pasa: El step de captura no está antes del deploy.
Cómo corregir: Step explícito Capture previous release ANTES del deploy. Output del job propagado para uso posterior.
3. Mismo secret en staging y producción
Síntoma: Bug en staging usa una API key de producción y afecta sistemas reales.
Por qué pasa: Por simplicidad usaron el mismo secret.
Cómo corregir: Secrets diferentes por environment. Stripe test keys en staging, live keys en producción. Mismo principio para todo.
4. Approval gate sin notificar reviewers
Síntoma: El job production espera 2 horas porque ningún reviewer vio la notificación.
Por qué pasa: Reviewers no configurados para recibir notifs de GitHub.
Cómo corregir: En Settings → Notifications → enable "Workflow runs". Agregar también notification a Slack si el equipo prefiere.
5. No validar que el deploy efectivamente sucedió
Síntoma: El step deploy.sh retorna 0 pero el deploy en realidad falló.
Por qué pasa: El script no valida con health check post-deploy.
Cómo corregir: smoke_tests.sh después del deploy, antes de continuar. Si los smoke tests fallan, el job falla y el approval no se dispara.
Diagnóstico
Pregunta 1: ¿Tu workflow tiene `concurrency: cancel-in-progress: false` para deploys?
Si no, deploys consecutivos pueden cancelarse a mitad — peligroso.
Pregunta 2: ¿Capturas previous_release antes del deploy?
Sin esto, no puedes revertir programáticamente desde Phase 4.
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: ¿Configuraste required reviewers en environment "production"?
Sin esto, no hay gate humana — el deploy continúa automáticamente.
Pregunta 5: ¿Branch protection rules previenen pushes directos a main?
Sin esto, alguien puede saltar todo el pipeline pusheando directo. Pérdida de control.
Ejercicios
Ejercicio 1: Configurar environments y branch protection (Fácil)
Antes de implementar el workflow:
- Crear environments
stagingyproductioncon la configuración mostrada - Configurar branch protection para
main - Verificar que solo se puede mergear vía PR
Ejercicio 2: Implementar Phase 2 + 3 (Medio)
Implementa los 5 jobs (generate-changelog, readiness, deploy-staging, validate-staging, deploy-production). Verifica:
- Phase 2 corre paralelo (changelog + readiness)
- Phase 3 secuencial
- El approval gate se activa antes de production
Probarlo end-to-end mergeando un PR de prueba.
Ejercicio 3: Implementar get_current_release.sh real (Difícil)
Reemplaza el mock con la lógica real de tu plataforma:
- Si usas Kubernetes: kubectl + labels
- Si usas Heroku: heroku releases API
- Si usas otro: la API/CLI correspondiente
Verifica que después de un deploy, get_current_release.sh production retorna el nuevo release.
Resumen
- Phase 2 paralela: changelog + readiness en simultáneo
- Phase 3 secuencial: staging → validate → approval → production
cancel-in-progress: falsepara deploys (nunca cancelar)- Capture previous release ANTES del deploy — necesario para rollback (Phase 4)
- Environments configurados: staging sin reviewers, production con required reviewers
- Secrets separados por environment
- Branch protection en main previene saltar el pipeline
- Reuso de scripts de Módulos 4 y 5 — no reescribir
Próxima cápsula: 05 — Failure paths y observabilidad. Las phases 2-3 funcionan en happy path. Pero tienes que cubrir los casos donde algo falla: deploy a staging falla, métricas degradan en producción, approval rechazado. La cápsula 05 cubre todos los failure modes con el sistema de monitoring + auto-rollback de Módulo 5.
Recursos Adicionales
- GitHub Environments — Configuración completa
- GitHub Branch Protection — Reglas de protección
- GitHub Actions: concurrency — cancel-in-progress
- GitHub Actions: needs — Dependencias entre jobs
- The Twelve-Factor App: Backing services — Por qué staging y prod deben diferir
- Argo Rollouts — Para deployments avanzados (canary, blue-green)