Módulo 6: Deployment Pipelines

2. CD Concepts y Estrategias

Descripción

En la cápsula anterior viste la diferencia entre CI y CD a nivel general. Ahora profundizas en los conceptos fundamentales de Continuous Delivery: qué es, cómo se diferencia de Continuous Deployment, cuándo usar cada approach, y cómo se estructura un deployment pipeline. Esta distinción no es académica — define cómo diseñas tu pipeline y qué nivel de automatización aplicas.

Para sistemas AI, la elección entre Delivery y Deployment tiene implicaciones directas. Un modelo que genera respuestas incorrectas en producción no solo causa bugs — puede generar costos significativos en API calls, producir outputs que confunden a los usuarios, o violar constraints de negocio. Por eso, la mayoría de equipos que trabajan con AI eligen Continuous Delivery (con aprobación humana) sobre Continuous Deployment (totalmente automático). El humano en el loop no es debilidad — es prudencia.

Esta cápsula te da el marco conceptual para entender el deployment pipeline que construyes en el resto del módulo.


Continuous Delivery vs Continuous Deployment

Continuous Delivery

Definición: Cada cambio que pasa los tests está listo para ir a producción, pero requiere una acción manual (un click, una aprobación) para desplegarse.

Push → CI Pipeline → Imagen lista → ⏸️ Aprobación humana → Deploy producción
                                          ↑
                                     "¿Está bien?"

El pipeline llega hasta "la imagen está en el registry y el staging funciona." Pero no deploya a producción automáticamente. Un humano revisa, verifica staging, y aprueba.

Características:

  • 📋 Siempre deployable — Cada commit que pasa CI puede ir a producción
  • 📋 Control humano — Alguien decide cuándo se despliega
  • 📋 Frecuencia variable — Puedes deployear una vez al día o una vez por semana
  • 📋 Safety net — Si staging muestra algo inesperado, no deployeas

Continuous Deployment

Definición: Cada cambio que pasa los tests se despliega a producción automáticamente. No hay paso manual.

Push → CI Pipeline → Imagen lista → Deploy producción (automático)

No hay pausa. Si los tests pasan, el código va a producción. El pipeline es completamente autónomo.

Características:

  • 📋 Totalmente automatizado — De push a producción sin intervención
  • 📋 Velocidad máxima — Cambios llegan a usuarios en minutos
  • 📋 Requiere confianza total en tests — Si los tests no atrapan un bug, producción lo sufre
  • 📋 Feedback rápido — Descubres problemas en producción inmediatamente

La comparación directa

AspectoContinuous DeliveryContinuous Deployment
Último pasoManual (approval)Automático
Velocidad a producciónMinutos a horasMinutos
ControlHumano decide cuándoPipeline decide siempre
RiesgoMenor (humano verifica)Mayor (depende de tests)
RequiereTests buenos + reviewerTests excelentes + monitoring
FrecuenciaCuando el reviewer apruebaCada push
RollbackMenos frecuente (prevenido por review)Más frecuente (automatizado)

Visualmente

Continuous Integration (CI):
  Code → Build → Test → ✅ "El código es válido"
  
Continuous Delivery (CD - Delivery):
  Code → Build → Test → Deploy Staging → ⏸️ Approval → Deploy Prod
                                              ↑
                                         Humano revisa
  
Continuous Deployment (CD - Deployment):
  Code → Build → Test → Deploy Staging → Deploy Prod (automático)
                                              ↑
                                         Sin intervención

Por qué Continuous Delivery es mejor para sistemas AI

El argumento principal

Los sistemas AI tienen un problema que las aplicaciones tradicionales no tienen: comportamiento no determinista. Un CRUD siempre devuelve los mismos datos para la misma query. Un endpoint que llama a GPT-4 puede devolver respuestas diferentes cada vez, y no todos los fallos son capturables por tests automatizados.

Escenario: Cambias el system prompt de tu chatbot

Tests automatizados:
  ✅ El endpoint responde 200
  ✅ La respuesta es un string no vacío
  ✅ El formato JSON es correcto
  ✅ Los tokens usados están dentro del límite
  
Pero los tests NO verifican:
  ❓ ¿La respuesta es útil para el usuario?
  ❓ ¿El tono es apropiado para el contexto?
  ❓ ¿El modelo no está alucinando datos?
  ❓ ¿La latencia es aceptable bajo carga?

Un humano que revisa staging puede detectar estos problemas. Los tests automatizados no pueden (al menos no completamente, no todavía).

Cuándo cada approach es apropiado

Usa Continuous Delivery (con approval) cuando:

  • 📋 Tu aplicación llama a LLMs (GPT, Claude, etc.) — outputs no deterministas
  • 📋 Cambias prompts o configuración de modelos — impacto difícil de testear automáticamente
  • 📋 Tu equipo es pequeño (1-5 personas) — el overhead de approval es mínimo
  • 📋 Los costos por error son altos — API calls innecesarias, datos incorrectos
  • 📋 Estás en una etapa temprana — tu test suite no cubre todos los edge cases

Usa Continuous Deployment (automático) cuando:

  • 📋 Tu test suite es muy madura — >90% coverage, integration tests, load tests
  • 📋 Tienes monitoring robusto — alertas inmediatas cuando algo falla en producción
  • 📋 Los cambios son de bajo riesgo — typos, UI tweaks, config changes
  • 📋 Tu equipo deployea muchas veces al día — el overhead de approval es cuello de botella
  • 📋 Tienes rollback automático confiable — puedes revertir en segundos

La recomendación para esta guía

Para tu deployment pipeline:

  ✅ Continuous Delivery con approval gate

  Push → Test → Build → Deploy Staging → ⏸️ Reviewer aprueba → Deploy Producción

  ¿Por qué?
  1. Tu test suite está en construcción (no es exhaustiva todavía)
  2. Tus prompts cambian frecuentemente (impacto no determinista)
  3. API keys de producción cuestan dinero real
  4. El overhead de un reviewer aprobando es < 5 minutos
  5. La paz mental de saber que alguien verificó vale más que la velocidad

Cuando tu test suite sea madura y tengas monitoring robusto (después de la guía #18), puedes evolucionar a Continuous Deployment. Pero empezar con Delivery es la decisión prudente.


El Deploy Pipeline Pattern

Anatomía de un deployment pipeline

Todo deployment pipeline sigue un patrón de stages progresivos. Cada stage agrega confianza:

Stage 1: Build
  → El código compila/construye sin errores
  → Confianza: "El código es sintácticamente válido"

Stage 2: Test
  → Los tests unitarios e integración pasan
  → Confianza: "El código hace lo que esperamos"

Stage 3: Package
  → La imagen Docker se construye y pushea
  → Confianza: "El código corre en un contenedor"

Stage 4: Deploy Staging
  → La imagen se despliega a un ambiente real
  → Confianza: "El código funciona en un servidor real"

Stage 5: Validate
  → Smoke tests verifican funcionalidad básica
  → Confianza: "La aplicación responde correctamente"

Stage 6: Approve
  → Un humano verifica y aprueba
  → Confianza: "Un experto revisó y está de acuerdo"

Stage 7: Deploy Production
  → La imagen se despliega a producción
  → Confianza: "Los usuarios tienen la nueva versión"

Stage 8: Verify
  → Smoke tests en producción + monitoring
  → Confianza: "Producción está estable"

El principio de confianza progresiva

Cada stage es más caro de ejecutar y más cercano al usuario. Si un cambio va a fallar, quieres que falle lo antes posible — en el stage más barato:

                            Costo del failure
Stage 1: Build              │█                      Bajo (solo CI minutes)
Stage 2: Test               │██                     Bajo (solo CI minutes)  
Stage 3: Package            │███                    Medio (build + push)
Stage 4: Deploy Staging     │█████                  Medio (servidor staging)
Stage 5: Validate           │██████                 Medio (smoke tests)
Stage 6: Approve            │███████                Alto (tiempo humano)
Stage 7: Deploy Production  │█████████████          Muy alto (usuarios afectados)
Stage 8: Verify             │████████████████       Máximo (producción inestable)

Si tus tests son buenos, la mayoría de los fallos se detectan en stages 1-3. Staging detecta problemas de ambiente. Approval detecta problemas de negocio. Producción debería recibir solo cambios validados.


Deploy Pipeline en GitHub Actions

Estructura básica del pipeline

name: Deploy Pipeline

on:
  push:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: pip install -r requirements-dev.txt
      - run: pytest tests/ -v

  build:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          tags: ghcr.io/${{ github.repository }}:sha-${{ github.sha }}

  deploy-staging:
    needs: build
    runs-on: ubuntu-latest
    environment: staging
    steps:
      - name: Deploy to staging
        run: echo "Deploying to staging..."

  deploy-production:
    needs: deploy-staging
    runs-on: ubuntu-latest
    environment:
      name: production
    steps:
      - name: Deploy to production
        run: echo "Deploying to production..."

La cadena de needs

test → build → deploy-staging → deploy-production

Cada job depende del anterior. Si test falla, nada más se ejecuta. Si build falla, no hay deploy. Si deploy-staging falla, producción no se toca.

La clave está en environment: — cuando un job declara un environment con protection rules, GitHub pausa el workflow hasta que las reglas se cumplan (aprobación del reviewer, wait timer, etc.).

Diferencia entre el CI pipeline y el Deploy pipeline

CI Pipeline (Módulos 1-5):
  Trigger: push + PR
  Jobs: test → build → scan → push
  Output: Imagen en GHCR
  
Deploy Pipeline (este módulo):
  Trigger: solo push a main (no PRs)
  Jobs: test → build → deploy-staging → approve → deploy-production
  Output: Aplicación corriendo en producción

Puedes tenerlos como workflows separados o como un solo workflow combinado. En esta guía los mantenemos separados por claridad, pero en el Módulo 8 (proyecto integrador) los combinamos.


Environments como stages del pipeline

Mapping de environments a stages

GitHub Environments mapean directamente a los stages de tu deployment pipeline:

GitHub Environment: staging
  → Sin protection rules (deploy automático)
  → Secrets: OPENAI_API_KEY (budget limitado), SERVER_SSH_KEY (staging)
  → URL: https://staging.tu-app.com

GitHub Environment: production
  → Protection rules: required reviewer, wait timer
  → Secrets: OPENAI_API_KEY (producción), SERVER_SSH_KEY (producción)
  → URL: https://tu-app.com

Cada environment tiene sus propios secrets. Staging usa una API key con budget limitado ($10/mes). Producción usa la API key real. Si staging tiene un bug que hace retry loops, los costos están acotados.

El flujo de secrets por environment

deploy-staging:
  environment: staging
  steps:
    - run: echo "Using staging secrets"
      env:
        OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
        # Valor: sk-staging-abc123 (budget: $10/mes)

deploy-production:
  environment: production
  steps:
    - run: echo "Using production secrets"
      env:
        OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
        # Valor: sk-prod-xyz789 (sin límite)

El nombre del secret es el mismo (OPENAI_API_KEY), pero el valor es diferente por environment. El workflow no necesita saber la diferencia — GitHub inyecta el valor correcto según el environment declarado.


Pipeline triggers: cuándo deployear

Solo main, nunca PRs

El deploy pipeline se triggerea solo en push a main, no en PRs:

on:
  push:
    branches: [main]

Por qué no en PRs: Un PR es código propuesto, no aceptado. Deployear código que no ha sido mergeado a main es peligroso — podría sobreescribir staging con código experimental.

Push a main + tags para releases

on:
  push:
    branches: [main]
    tags: ["v*"]

Esto triggerea el pipeline tanto en push a main (deploy automático a staging) como en tags de versión (deploy de release).

Workflow dispatch para deploys manuales

on:
  push:
    branches: [main]
  workflow_dispatch:
    inputs:
      environment:
        description: "Target environment"
        required: true
        type: choice
        options:
          - staging
          - production
      image_tag:
        description: "Image tag to deploy"
        required: true
        type: string

workflow_dispatch permite deployear manualmente desde la UI de Actions. Útil para rollbacks (deployear un tag anterior) o re-deploys.


Comparaciones

Deployment pipeline completo vs deploy simple

AspectoDeploy simplePipeline completo
StagesBuild → deployBuild → test → staging → approve → production
Tiempo2-3 min5-10 min
RiesgoAlto (sin validación)Bajo (staging + approval + rollback)
RollbackManualAutomatizado
Confianza"Debería funcionar""Staging funciona, reviewer aprobó"

Un workflow vs workflows separados

AspectoTodo en un workflowWorkflows separados
LegibilidadUn archivo grandeArchivos enfocados
TriggerMismo trigger para todoTriggers independientes
ReusabilidadDifícil de reusarCada workflow es independiente
DebuggingUn workflow largoFácil de aislar problemas
RecomendaciónPara el proyecto final (Módulo 8)Para aprender (este módulo)

Self-hosted runner vs GitHub-hosted runner

AspectoGitHub-hostedSelf-hosted
SetupCero (listo de fábrica)Necesitas configurar el runner
CostoIncluido (2000 min/mes free)Tu infraestructura
RedInternet públicoAcceso a red privada
Deploy a servidorSSH o API externaAcceso directo al servidor
Para esta guíaSuficientePara cuando necesites red privada

Troubleshooting

1. "Environment 'staging' not found"

Síntoma:

Error: Environment 'staging' was not found.

Causa: El environment no está creado en GitHub, o el nombre tiene un typo.

Solución:

GitHub → Settings → Environments → New environment
  Name: staging (exactamente como en el workflow YAML)
  → Configure environment

Verifica que el nombre en el YAML coincida exactamente con el nombre del environment en GitHub (case-sensitive).

2. Deploy job se queda en "Waiting"

Síntoma: El job muestra "Waiting for review" pero no hay reviewer configurado.

Causa: El environment tiene protection rules con required reviewers, pero nadie puede aprobar.

Solución:

GitHub → Settings → Environments → production → Protection rules
  Required reviewers: Agrega tu username o un team

Si estás practicando solo, agrégarte a ti mismo como reviewer — puedes aprobar tus propios deploys.

3. El workflow no se triggerea en push a main

Síntoma: Push a main, pero el deploy workflow no aparece en Actions.

Causa: El archivo YAML no está en .github/workflows/, tiene un error de syntax, o el branch filter no coincide.

Solución:

# Verifica que el archivo existe
ls .github/workflows/deploy.yml

# Valida el YAML
python -c "import yaml; yaml.safe_load(open('.github/workflows/deploy.yml'))"

# Verifica el trigger
# El YAML debe tener:
# on:
#   push:
#     branches: [main]

4. Secrets de environment no se inyectan

Síntoma: El step usa ${{ secrets.MY_SECRET }} pero el valor está vacío.

Causa: El job no declara environment: o el secret está en un environment diferente.

Solución:

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: staging    # NECESARIO para acceder a secrets de staging
    steps:
      - run: echo "Key length: ${#KEY}"
        env:
          KEY: ${{ secrets.OPENAI_API_KEY }}

Sin la línea environment: staging, el job no puede acceder a los secrets del environment.


Ejercicios

Ejercicio 1: Identifica CI vs CD

Para cada escenario, indica si es CI, Continuous Delivery, o Continuous Deployment:

  1. Push triggerea tests y lint automáticamente
  2. Si tests pasan, la imagen se pushea a GHCR
  3. La imagen se despliega a staging automáticamente
  4. Un reviewer revisa staging y aprueba
  5. La imagen se despliega a producción después de la aprobación
  6. Cada push a main despliega a producción sin intervención humana
Ver solución
  1. CI — Validación automática del código
  2. CI — Packaging automático (parte del build pipeline)
  3. CD (Delivery o Deployment) — Deployment automático a staging (común en ambos)
  4. CD (Delivery) — Approval humano antes de producción
  5. CD (Delivery) — Deploy post-approval
  6. CD (Deployment) — Totalmente automático, sin intervención

El flujo 1-5 es Continuous Delivery (approval gate antes de producción). El flujo 1-3 + 6 es Continuous Deployment (sin approval).

Ejercicio 2: Diseña la cadena de jobs

Tienes estos jobs: lint, test, build-docker, deploy-staging, smoke-test, deploy-production. Define la cadena de needs: para que: (1) lint y test corran en paralelo, (2) docker dependa de ambos, (3) staging dependa de docker, (4) smoke-test dependa de staging, (5) production dependa de smoke-test.

Ver solución
jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: ruff check src/

  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: pytest tests/ -v

  build-docker:
    needs: [lint, test]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          tags: ghcr.io/${{ github.repository }}:sha-${{ github.sha }}

  deploy-staging:
    needs: build-docker
    runs-on: ubuntu-latest
    environment: staging
    steps:
      - run: echo "Deploy to staging"

  smoke-test:
    needs: deploy-staging
    runs-on: ubuntu-latest
    steps:
      - run: curl -sf https://staging.app.com/health

  deploy-production:
    needs: smoke-test
    runs-on: ubuntu-latest
    environment:
      name: production
    steps:
      - run: echo "Deploy to production"

La cadena es:

lint ──┐
       ├──→ build-docker → deploy-staging → smoke-test → deploy-production
test ──┘

lint y test corren en paralelo porque no tienen needs:. build-docker espera a que ambos terminen (needs: [lint, test]). El resto es secuencial.

Ejercicio 3: Configura triggers para un deploy pipeline

Crea la sección on: de un workflow que: (1) se triggerea en push a main, (2) se triggerea en tags que empiezan con v, (3) se puede triggerear manualmente con un input para elegir el environment (staging o production) y otro para el image tag.

Ver solución
name: Deploy Pipeline

on:
  push:
    branches: [main]
    tags: ["v*"]

  workflow_dispatch:
    inputs:
      environment:
        description: "Target environment"
        required: true
        type: choice
        options:
          - staging
          - production
      image_tag:
        description: "Image tag to deploy (e.g., sha-abc1234 or v1.2.3)"
        required: true
        type: string

Puntos clave:

  • branches: [main] filtra push solo a main (no a feature branches)
  • tags: ["v*"] matchea tags como v1.0.0, v2.1.3, etc.
  • workflow_dispatch habilita el botón "Run workflow" en la UI de Actions
  • type: choice crea un dropdown con opciones fijas
  • type: string es un input de texto libre para el tag

Ejercicio 4: Elige la estrategia para cada escenario

Para cada escenario, elige Continuous Delivery o Continuous Deployment y justifica:

  1. Startup de 3 personas con un chatbot AI que cambia prompts diariamente
  2. Empresa con test suite madura, monitoring con PagerDuty, y 50 deploys/día
  3. Proyecto personal de un portfolio que solo tú usas
  4. Equipo de ML que despliega modelos que clasifican transacciones bancarias
Ver solución
  1. Continuous Delivery. Prompts que cambian diariamente generan outputs no deterministas. Con 3 personas, el overhead de approval es bajo (< 5 min). Un humano verificando staging previene que un prompt roto llegue a usuarios.

  2. Continuous Deployment. Test suite madura + monitoring robusto + alta frecuencia de deploys. El overhead de approval 50 veces al día es insostenible. Confianza en tests + rollback automático + alertas inmediatas permiten deploy automático.

  3. Continuous Deployment. Es tu proyecto, solo tú lo usas. El riesgo es mínimo y el overhead de aprobarte a ti mismo no agrega valor. Si algo se rompe, lo ves inmediatamente.

  4. Continuous Delivery (con approval estricto). Clasificar transacciones bancarias es de alto riesgo. Un modelo incorrecto puede aprobar transacciones fraudulentas o bloquear transacciones legítimas. Requiere aprobación de múltiples reviewers, posiblemente con wait timer de 24 horas para observación en staging.


Resumen

  • Continuous Delivery = listo para deploy + aprobación humana antes de producción
  • Continuous Deployment = deploy automático a producción sin intervención
  • Para AI, Delivery es la recomendación — outputs no deterministas necesitan ojo humano
  • El deploy pipeline pattern: build → test → staging → approve → production
  • Confianza progresiva: cada stage agrega confianza, cada fallo es más barato temprano
  • Environments mapean a stages: staging (automático), production (con approval)
  • Triggers: push a main para deploys automáticos, workflow_dispatch para manuales
  • CI produce artefactos; CD los lleva a producción — son complementarios, no sustitutos

Recursos adicionales

  1. Continuous Delivery vs Continuous Deployment — La diferencia explicada por Atlassian
  2. GitHub Actions — Using environments — Environments como stages
  3. Martin Fowler — Continuous Delivery — El concepto original por el co-autor del libro
  4. The Twelve-Factor App — Build, Release, Run — Separación de stages en apps modernas
  5. GitHub Actions — Workflow syntax — Referencia de needs:, environment:, triggers
  6. Accelerate (book) — Investigación sobre deployment frequency y estabilidad