El pipeline de Reservalia construye una imagen de contenedor perfectamente correcta en cada pull request… y la tira a la basura cuando el runner muere. Falta la pieza que convierte una verificación en algo con lo que se puede desplegar: guardar el resultado, ponerle un nombre que signifique algo y hacerlo viajar entre entornos sin volver a construirlo. Esta lección gira alrededor de un principio único —construir una vez, desplegar muchas veces— y de todo lo que se deriva de él: qué es un artefacto inmutable, por qué reconstruir en cada entorno destruye la trazabilidad, qué estrategias de versionado existen y cuándo usar cada una, por qué la etiqueta latest es peligrosa dentro de un pipeline, dónde se guardan los artefactos y cuánto cuestan, cómo se promociona el mismo reservalia/api:a3f9c21 de dev a staging y a prod, y cómo averiguar desde una máquina en producción qué commit exacto está ejecutando. Lo que no veremos es cómo se despliega ese artefacto: eso es el módulo 3 entero.

Contenido

  1. Construir una vez, desplegar muchas veces
  2. El artefacto inmutable y el antipatrón de reconstruir por entorno
  3. Estrategias de versionado
  4. Por qué latest es peligrosa
  5. Versionado automático a partir de commits convencionales
  6. Dónde se guardan los artefactos: registros y retención
  7. Promoción entre entornos
  8. Trazabilidad: saber qué está corriendo en producción
  9. Firma y procedencia del artefacto
  10. El job publicar de Reservalia
  11. Errores Comunes y Consejos
  12. Ejercicios
  13. Conclusión

  1. Construir una vez, desplegar muchas veces

El principio. El pipeline construye un solo artefacto por commit. Ese artefacto, sin modificar ni reconstruir, es el que se prueba en dev, el que se valida en staging y el que acaba sirviendo peticiones en prod. Lo único que cambia entre entornos es la configuración inyectada desde fuera.

Ya apareció en la lección 01-04, cuando definimos los tres entornos de Reservalia. Ahora podemos ver por qué es tan importante: si reconstruyes en cada entorno, lo que pruebas y lo que despliegas son objetos distintos, y todas las pruebas del mundo dejan de garantizar nada sobre producción.

Las diferencias no tienen que ser dramáticas para hacer daño. Basta con que entre la build de staging (martes) y la de prod (jueves) una dependencia transitiva haya publicado un parche, o con que el runner haya cambiado de imagen base. Dos builds del mismo commit separadas por 48 horas pueden no ser el mismo software.

  1. El artefacto inmutable y el antipatrón de reconstruir por entorno

Un artefacto es la unidad desplegable que produce el pipeline: en Reservalia, la imagen reservalia/api:a3f9c21 y el paquete de ficheros estáticos de apps/web. Que sea inmutable significa que, una vez publicado con una etiqueta, ese contenido no cambia jamás. Si hay que corregir algo, se publica un artefacto nuevo con otra etiqueta.

flowchart LR
    subgraph BIEN["Construir una vez"]
        C1["commit a3f9c21"] --> B1["build"] --> A1["reservalia/api:a3f9c21"]
        A1 --> D1[dev] --> S1[staging] --> P1[prod]
    end
    subgraph MAL["Reconstruir por entorno"]
        C2["commit a3f9c21"] --> B2["build dev"] --> D2[dev]
        C2 --> B3["build staging"] --> S2[staging]
        C2 --> B4["build prod"] --> P2["prod ← ¿es lo que probaste?"]
    end

Lo que se pierde al reconstruir por entorno:

Se pierde Consecuencia práctica
Trazabilidad "Funciona en staging y falla en prod" pasa a ser irresoluble
Validez de las pruebas Probaste el artefacto A y desplegaste el B
Tiempo Tres builds en lugar de una
Rollback fiable Volver atrás exige reconstruir, y quizá ya no salga igual

Y el corolario incómodo: si el artefacto es el mismo en los tres entornos, no puede contener nada específico de un entorno. Ni la URL de la base de datos, ni la clave de la pasarela de pago, ni el nivel de log. Todo eso entra por variables de entorno o por un gestor de secretos en el momento de arrancar. Un artefacto que se construye con NODE_ENV=staging cocido dentro no es promocionable.

  1. Estrategias de versionado

Ponerle nombre al artefacto no es cosmético: es lo que permite hablar de él sin ambigüedad.

Estrategia Ejemplo Ventaja Inconveniente Cuándo usarla
SemVer 2.4.1 Comunica el impacto del cambio Requiere decidir el número Librerías y APIs públicas
SHA del commit a3f9c21 Único y trazable al instante No dice nada al humano Servicios desplegados de forma continua
git describe v2.4.0-13-ga3f9c21 Legible y trazable Requiere etiquetas y fetch-depth: 0 Cuando quieres ambas cosas
Por fecha 2026.03.02.1 Ordenable, intuitivo No identifica el código Releases periódicas
Incremental build-1284 Simple Se pierde al cambiar de herramienta Sistemas antiguos

Reservalia usa el SHA corto como etiqueta primaria: reservalia/api:a3f9c21. La razón es que es una plataforma SaaS con un único despliegue —no distribuye versiones a clientes— y lo que necesita es responder en un segundo a la pregunta "¿qué código está corriendo?". SemVer resolvería un problema que Reservalia no tiene.

Nada impide combinar estrategias: la misma imagen puede llevar varias etiquetas apuntando al mismo contenido.

reservalia/api:a3f9c21           # identidad: el commit exacto (nunca cambia)
reservalia/api:v2.4.0            # release legible para las personas
reservalia/api:main              # puntero móvil al último main verde

Solo la primera es inmutable; las otras dos son punteros que pueden reasignarse.

  1. Por qué latest es peligrosa

latest no es una versión: es una etiqueta móvil que apunta a lo último que alguien subió. Dentro de un pipeline provoca cuatro problemas concretos:

  1. No es reproducible. docker pull reservalia/api:latest hoy y mañana pueden traer imágenes distintas. Si un contenedor se reinicia solo, puede levantarse con otra versión.
  2. Rompe el rollback. "Vuelve a la versión anterior" no tiene respuesta: latest no guarda historial.
  3. Hace imposible el diagnóstico. Dos máquinas del mismo servicio pueden estar ejecutando código distinto y ambas reportar latest.
  4. Es una carrera. Dos pipelines simultáneos escriben la misma etiqueta y gana el último en terminar, que no tiene por qué ser el commit más reciente.

La regla: en un pipeline, cada despliegue referencia una etiqueta inmutable. Si quieres una etiqueta amable para tu portátil, adelante; para desplegar, el SHA.

  1. Versionado automático a partir de commits convencionales

Las convenciones de commit de la lección anterior habilitan algo muy práctico: deducir la versión siguiente leyendo el historial, sin que nadie decida nada a mano.

Commits desde la última versión Salto De 2.4.1 a
Solo fix:, chore:, docs: Parche 2.4.2
Al menos un feat: Menor 2.5.0
Alguno con BREAKING CHANGE: Mayor 3.0.0

Herramientas como semantic-release o Changesets hacen tres cosas en un solo paso: calculan la versión, generan el changelog agrupando los commits por tipo, y crean la etiqueta de git y la release. El changelog resultante se escribe solo:

## 2.5.0 (2026-03-02)
### Funcionalidades
* **citas:** permitir reservas recurrentes semanales (a3f9c21)
### Correcciones
* **agenda:** no ofrecer huecos solapados con el descanso (b7e2d10)

Reservalia lo adopta en modo mixto y por un motivo muy concreto: el SHA sigue siendo la identidad del artefacto —es lo que se despliega y lo que aparece en /version—, mientras que la versión SemVer y el changelog sirven para comunicarse con las personas: notas de release, avisos a los negocios y el mensaje de la incidencia cuando algo se rompe.

  1. Dónde se guardan los artefactos: registros y retención

No todos los artefactos son iguales ni deben vivir en el mismo sitio.

Destino Qué guarda Vida útil Uso en Reservalia
actions/upload-artifact Ficheros del propio workflow Días Pasar dist/ entre jobs, informes de cobertura
Amazon ECR Imágenes de contenedor Meses o años reservalia/api:a3f9c21, lo que se despliega
GitHub Packages Imágenes y paquetes npm Configurable Alternativa cuando todo vive en GitHub
S3 Ficheros estáticos Años El dist/ de apps/web servido por CloudFront

Los artefactos efímeros del workflow resuelven el problema que vimos en la 02-01: los jobs no comparten disco. Si build genera algo que publicar necesita, hay que pasarlo explícitamente:

      - uses: actions/upload-artifact@v4          # en el job build
        with:
          name: web-dist
          path: apps/web/dist
          retention-days: 7                       # ← por defecto son 90

      - uses: actions/download-artifact@v4        # en el job que lo consume
        with: { name: web-dist, path: apps/web/dist }

retention-days merece atención porque el almacenamiento se factura. Un informe de cobertura de 40 MB por cada ejecución, con 20 ejecuciones diarias y 90 días de retención, son unos 72 GB de basura acumulada. Política razonable en Reservalia: 7 días para informes y artefactos intermedios; para las imágenes de ECR, una regla de ciclo de vida que conserve las 30 últimas de main y borre las de ramas de PR a los 14 días. Lo que nunca se borra automáticamente es una imagen que esté desplegada en algún entorno.

  1. Promoción entre entornos

Promocionar es declarar que un artefacto ya validado avanza al siguiente entorno. No se reconstruye, no se recompila: se señala.

flowchart LR
    M["merge en main<br/>commit a3f9c21"] --> B["build + publicar"]
    B --> ECR["ECR<br/>reservalia/api:a3f9c21"]
    ECR --> D["dev<br/>automático"]
    D -- "pruebas humo OK" --> S["staging<br/>automático"]
    S -- "validación + aprobación" --> P["prod"]
    P --> T["etiqueta prod-2026-03-02<br/>sobre la MISMA imagen"]

Dos formas de registrar el avance, con implicaciones distintas:

  • Etiquetas adicionales sobre el mismo digest. Al promocionar, se añade staging o prod-2026-03-02 a la imagen existente. Es visible desde el propio registro y no copia bytes.
  • Metadatos externos. Una tabla o un fichero versionado que dice qué SHA está en cada entorno. Encaja mejor con GitOps y deja un historial auditable.

Lo que ambas comparten, y es lo esencial: el contenido no cambia. Un digest —sha256:4f3c…— identifica los bytes exactos, y ese digest es el mismo en dev, en staging y en prod. Las etiquetas son nombres; el digest es la identidad.

Cómo se ejecuta cada promoción (aprobaciones manuales, despliegue progresivo, rollback) es el contenido del módulo 3.

  1. Trazabilidad: saber qué está corriendo en producción

Son las 23:40, hay un incidente y la primera pregunta de Nuria es siempre la misma: "¿qué versión hay desplegada?". Debe poder responderse en menos de un minuto, desde fuera y sin acceso privilegiado. Dos mecanismos, complementarios.

Etiquetas OCI en la imagen. Metadatos estándar incrustados en la propia imagen:

ARG COMMIT_SHA
ARG FECHA_BUILD
LABEL org.opencontainers.image.revision="${COMMIT_SHA}" \
      org.opencontainers.image.created="${FECHA_BUILD}" \
      org.opencontainers.image.source="https://github.com/reservalia/reservalia" \
      org.opencontainers.image.version="2.5.0"

Se consultan sin arrancar el contenedor con docker inspect, y sobreviven aunque alguien renombre la etiqueta.

Un endpoint /version. La forma más rápida y la que no requiere acceso a la infraestructura:

// apps/api/src/rutas/version.ts
export const version = {
  commit:    process.env.COMMIT_SHA    ?? 'desconocido',   // a3f9c21
  version:   process.env.APP_VERSION   ?? '0.0.0',         // 2.5.0
  construido:process.env.FECHA_BUILD   ?? 'desconocido',   // 2026-03-02T09:14:00Z
  entorno:   process.env.ENTORNO       ?? 'desconocido',   // prod
};
// GET /version → { "commit": "a3f9c21", "version": "2.5.0", ... }

Los valores se inyectan en el docker build como ARG y se convierten en ENV de la imagen. Detalle de seguridad: /version no debe revelar rutas internas, dependencias ni configuración; el SHA y la fecha son suficientes. Y detalle práctico: este endpoint es lo que permite comprobar que un despliegue ha surtido efecto. Si tras desplegar b7e2d10 el /version sigue diciendo a3f9c21, el despliegue no ha llegado, por mucho que el pipeline esté verde.

  1. Firma y procedencia del artefacto

Que un artefacto sea inmutable no demuestra quién lo construyó ni a partir de qué. Un atacante con acceso al registro podría publicar una imagen con la etiqueta esperada.

Dos mecanismos responden a eso: la firma criptográfica del artefacto (con herramientas como Sigstore/cosign, que permiten verificar antes de desplegar que la imagen la firmó tu pipeline) y la procedencia o provenance, un documento verificable que declara qué commit, qué workflow y qué runner produjeron ese digest, en la línea del marco SLSA. Ambos se apoyan además en un SBOM, el inventario de todo lo que contiene la imagen.

Los tres pertenecen a la cadena de suministro de software y se tratan en la lección 04-03, Seguridad en CI/CD. Aquí basta con saber que existen y que el paso previo —artefactos inmutables e identificados por su digest— ya está dado.

  1. El job publicar de Reservalia

  publicar:
    name: Publicar artefacto
    runs-on: ubuntu-22.04
    needs: [calidad, test, build]                      # 1
    if: github.ref == 'refs/heads/main'                # 2
    permissions:
      id-token: write                                  # 3
      contents: read
    steps:
      - uses: actions/checkout@v4

      - name: Calcular etiquetas
        id: meta
        run: |                                         # 4
          echo "sha_corto=$(git rev-parse --short=7 HEAD)" >> $GITHUB_OUTPUT
          echo "fecha=$(date -u +%Y-%m-%dT%H:%M:%SZ)"   >> $GITHUB_OUTPUT

      - name: Autenticarse en AWS
        uses: aws-actions/configure-aws-credentials@v4  # 5
        with:
          role-to-assume: ${{ secrets.AWS_ROLE_CI }}
          aws-region: eu-west-1

      - uses: aws-actions/amazon-ecr-login@v2
        id: ecr

      - name: Construir y publicar la imagen
        uses: docker/build-push-action@v5
        with:
          context: .
          file: apps/api/Dockerfile
          push: true                                    # 6
          tags: |
            ${{ steps.ecr.outputs.registry }}/reservalia/api:${{ steps.meta.outputs.sha_corto }}
            ${{ steps.ecr.outputs.registry }}/reservalia/api:main
          build-args: |
            COMMIT_SHA=${{ steps.meta.outputs.sha_corto }}
            FECHA_BUILD=${{ steps.meta.outputs.fecha }}
          cache-from: type=gha
          cache-to: type=gha,mode=max
  1. needs: [calidad, test, build] hace que este job espere a que los tres estén verdes. Es la garantía de que no se publica nada sin verificar.
  2. if: github.ref == 'refs/heads/main': en los pull requests se construye para comprobar, pero no se publica. Publicar cada PR llenaría el registro de imágenes que nadie desplegará.
  3. permissions: id-token: write habilita OIDC: el runner obtiene credenciales temporales de AWS asumiendo un rol, en lugar de guardar claves de acceso permanentes como secretos. Es la forma correcta de autenticarse contra la nube, y se detalla en la 04-03.
  4. $GITHUB_OUTPUT es el mecanismo para pasar valores entre steps del mismo job: lo que escribas ahí se lee luego como steps.meta.outputs.sha_corto.
  5. role-to-assume referencia el secreto AWS_ROLE_CI, que contiene el ARN del rol —no una credencial—.
  6. push: true con dos etiquetas apuntando a la misma imagen: el SHA corto, inmutable y trazable, y main, un puntero móvil cómodo para saber cuál es el último verde. Lo que se despliega es siempre la primera.

Con este job, el estado de Reservalia cambia de forma cualitativa: cada commit que entra en main deja un artefacto identificado, verificado y guardado, listo para desplegar.

Errores Comunes y Consejos

Error 1: reconstruir en cada entorno. El antipatrón central de la lección. Rompe la trazabilidad, invalida las pruebas y hace irreproducible el rollback.

Error 2: desplegar latest. No es reproducible, no permite volver atrás y hace imposible saber qué está corriendo. Usa siempre una etiqueta inmutable.

Error 3: meter configuración de entorno dentro del artefacto. Un artefacto con la URL de la base de datos de staging cocida dentro ya no se puede promocionar; habrá que reconstruirlo, y volvemos al error 1.

Error 4: publicar desde los pull requests. Llena el registro de imágenes que nadie usará y multiplica el coste de almacenamiento. Publica solo desde main o desde etiquetas.

Error 5: no fijar políticas de retención. El coste crece de forma silenciosa hasta que alguien mira la factura. Define retención desde el primer día, con la excepción de lo que esté desplegado.

Consejo 1: haz visible la versión. Un endpoint /version con el SHA es la mejor inversión de cinco minutos de todo el módulo; lo agradecerás a las 23:40 de un martes.

Consejo 2: piensa en digests, no en etiquetas. Las etiquetas son nombres reasignables; el digest sha256:… es la identidad real. Los sistemas de despliegue serios referencian digests.

Consejo 3: automatiza el changelog desde los commits. Si ya escribes commits convencionales, generarlo es gratis y el equipo deja de mantener a mano un fichero que nadie actualiza.

Ejercicios

Ejercicio 1

Un equipo tiene tres workflows: deploy-dev.yml, deploy-staging.yml y deploy-prod.yml. Los tres hacen checkout de main, npm ci, npm run build, construyen la imagen como app:latest y la despliegan. Enumera cuatro problemas y describe el pipeline correcto.

Ejercicio 2

Reservalia despliega reservalia/api:a3f9c21 en prod. A las dos horas aparece un error grave. Nuria quiere volver a la versión anterior, b7e2d10. Explica por qué esto es trivial con artefactos inmutables y qué habría que hacer si el equipo usara latest.

Ejercicio 3

Diseña el esquema de etiquetado de una imagen para un producto que se distribuye a clientes, con releases mensuales SemVer y correcciones urgentes entre ellas. Indica qué etiquetas pones, cuáles son inmutables y cuál usarías para desplegar.

Soluciones

Solución 1. Cuatro problemas: (1) se construye tres veces, así que lo desplegado en prod no es lo probado en staging —dos builds separadas por días pueden diferir—; (2) latest impide saber qué está corriendo y hace imposible el rollback; (3) hacer checkout de main en cada despliegue significa desplegar lo que haya ahora en la rama, no el commit validado, de modo que un merge intermedio se cuela sin querer en producción; (4) se triplica el tiempo y el coste de build sin obtener ninguna información adicional.

Pipeline correcto: un solo workflow de CI que, al entrar un commit en main, construye y publica reservalia/api:<sha> tras pasar calidad, test y build. Los tres despliegues son workflows separados que reciben como parámetro el SHA y se limitan a apuntar el entorno a esa imagen ya existente, promocionando el mismo digest de dev a staging y a prod.

Solución 2. Con artefactos inmutables, reservalia/api:b7e2d10 sigue existiendo en ECR sin modificar: el rollback consiste en decirle al servicio que use esa etiqueta y esperar a que arranquen las nuevas instancias. Son minutos, no requiere compilar nada y el resultado es exactamente el software que estuvo funcionando antes. Es justo el caso que reclamaba la frase de Nuria en el módulo 1 sobre deshacer un despliegue en cinco minutos.

Con latest, en cambio, no hay nada a lo que volver: la etiqueta apunta a la versión rota y la anterior no está identificada. Habría que averiguar qué commit era el bueno (sin registro fiable, probablemente mirando el historial de git a ojo), reconstruirlo —con el riesgo de que la nueva build no sea idéntica a la que funcionaba— y volver a publicar. En vez de minutos, media hora larga en plena incidencia, y con incertidumbre sobre el resultado.

Solución 3. Un esquema razonable:

Etiqueta ¿Inmutable? Para qué sirve
app:2.5.0 La release que se comunica al cliente
app:a3f9c21 Identidad exacta del commit; para diagnóstico
app:2.5 No Puntero al último parche de esa menor
app:2 No Puntero a la última menor de esa mayor
app:latest No Comodidad para pruebas locales; jamás para desplegar

Para desplegar se usa siempre una etiqueta inmutable, preferiblemente el SHA o, mejor aún, el digest sha256:…. Las etiquetas móviles 2.5 y 2 son un servicio a los clientes que quieren recibir parches automáticamente, y son una decisión de ellos, no tuya. Una corrección urgente sobre 2.5.0 se publica como 2.5.1 y reasigna los punteros 2.5, 2 y latest, sin tocar nunca las etiquetas inmutables anteriores.

Conclusión

Reservalia ya produce algo que se puede desplegar, y sabe exactamente qué es:

  • Construir una vez, desplegar muchas veces. Un artefacto por commit, el mismo en dev, staging y prod; lo único que cambia entre entornos es la configuración inyectada desde fuera. Reconstruir por entorno rompe la trazabilidad y anula el valor de las pruebas.
  • Un artefacto inmutable no cambia nunca una vez publicado. Si hay que corregir, se publica otro con otra etiqueta.
  • De las estrategias de versionado, Reservalia usa el SHA corto como identidad —lo que necesita un SaaS de despliegue continuo— y reserva SemVer para comunicarse con las personas. latest no se despliega jamás: no es reproducible, impide el rollback y es una carrera entre pipelines.
  • Los commits convencionales permiten deducir la versión y generar el changelog automáticamente, sin que nadie mantenga un fichero a mano.
  • Los artefactos viven en sitios distintos según su naturaleza —efímeros del workflow para pasar ficheros entre jobs, ECR para las imágenes desplegables, S3 para la web— y todos necesitan política de retención, porque el almacenamiento se factura.
  • Promocionar es señalar, no reconstruir: el mismo digest avanza de entorno en entorno, registrado con etiquetas adicionales o con metadatos externos.
  • La trazabilidad se resuelve con etiquetas OCI en la imagen y un endpoint /version que devuelve el commit; es también la forma de comprobar que un despliegue ha surtido efecto.
  • La firma y la procedencia del artefacto existen y son importantes; su desarrollo es la lección 04-03.
  • El job publicar depende de calidad, test y build, solo se ejecuta en main, se autentica en AWS por OIDC y sube a ECR la imagen etiquetada con el SHA y con main.

Ya tenemos los cuatro jobs y un artefacto publicado. Falta la pieza que conecta todo esto con la forma real de trabajar del equipo. En la siguiente lección, Integración con Control de Versiones, veremos cómo el modelo de ramas condiciona la CI —comparando trunk-based development, GitHub Flow y Git Flow—, por qué las ramas de vida larga son incompatibles con la Integración Continua, cómo se configuran las reglas de protección de main, CODEOWNERS y la merge queue, cómo evitar ejecuciones inútiles con paths y concurrency, y qué efecto tiene cada estrategia de merge sobre la trazabilidad del artefacto y sobre el lead time de la 01-05. Y cerraremos el módulo con el ci.yml completo de Reservalia.

Curso de CI/CD: Integración y Despliegue Continuo

Módulo 1: Introducción a CI/CD

Módulo 2: Integración Continua (CI)

Módulo 3: Despliegue Continuo (CD)

Módulo 4: Prácticas Avanzadas de CI/CD

Módulo 5: Implementación de CI/CD en Proyectos Reales

Módulo 6: Herramientas y Tecnologías

Módulo 7: Ejercicios Prácticos

Módulo 8: Recursos Adicionales

© Copyright 2026. Todos los derechos reservados