El problema que resuelve la entrega continua
Antes de automatizar nada, conviene entender qué se intenta evitar: las integraciones tardías. Cuando varias personas trabajan semanas sobre ramas largas, el momento de juntar el código se convierte en un evento doloroso, con conflictos, regresiones y una versión que nadie se atreve a publicar. La idea de fondo de CI/CD es la contraria: integrar y desplegar con tanta frecuencia que cada cambio sea pequeño, fácil de revisar y fácil de deshacer.
Un pipeline no es una herramienta, es un contrato: describe, en un fichero versionado junto al código, qué condiciones debe cumplir un cambio para llegar a producción y quién (o qué) las comprueba.
Integración, entrega y despliegue: tres cosas distintas
Integración continua
Entrega continua
Despliegue continuo: sin aprobación manual
Los tres términos se mezclan a menudo, pero describen niveles de automatización distintos.
| Práctica | Qué garantiza | Quién pulsa el botón de producción |
|---|---|---|
| Integración continua (CI) | Cada cambio se compila y se prueba en un entorno limpio y compartido | No aplica |
| Entrega continua | Además, siempre existe un artefacto verificado y listo para desplegar | Una persona, cuando el negocio lo decide |
| Despliegue continuo | Además, todo cambio que pasa las puertas llega a producción | El propio pipeline |
La entrega continua desacopla dos decisiones que suelen confundirse: desplegar (poner código en producción) y liberar (que los usuarios lo vean). Con banderas de funcionalidad se puede desplegar a diario y liberar cuando convenga.
Anatomía de un pipeline
Trabajos en paralelo
Cada job arranca en un runner limpio, con sus pasos (steps).
Lo que sobrevive entre jobs
Los conceptos básicos son los mismos en casi todas las plataformas (GitHub Actions, GitLab CI, Jenkins, CircleCI, Azure Pipelines), aunque cambie el vocabulario.
| Concepto | Descripción |
|---|---|
| Disparador | El evento que lanza el pipeline: push, pull request, etiqueta, programación con cron, lanzamiento manual o llamada desde otro workflow |
| Workflow o pipeline | La definición completa, escrita en YAML y versionada con el código |
| Job o trabajo | Un conjunto de pasos que se ejecutan en la misma máquina. Por defecto, los jobs son independientes y corren en paralelo |
| Step o paso | Un comando de shell o una acción reutilizable |
| Runner o agente | La máquina (alojada por la plataforma o propia) que ejecuta el job. Arranca limpia en cada ejecución si es efímera |
| Artefacto | Fichero producido por un job y conservado: paquete, imagen, informe de cobertura |
| Caché | Datos que se reutilizan entre ejecuciones para no repetir trabajo, como dependencias descargadas |
| Matriz | Un mismo job repetido con combinaciones de parámetros: versiones del lenguaje, sistemas operativos |
| Entorno | Destino con nombre (staging, producción) que puede llevar secretos, aprobaciones y reglas propias |
Runners alojados y propios
Los runners alojados por la plataforma son cómodos, aislados y se descartan tras cada uso. Los propios (self-hosted) permiten hardware especial, acceso a redes privadas o ahorrar costes, pero heredan el riesgo de la persistencia: si un job malicioso deja algo en la máquina, el siguiente lo encontrará. Los runners propios deben ser efímeros (una máquina o contenedor nuevo por job) y no deberían aceptar código de repositorios públicos sin aislamiento.
Las etapas y su orden
1. Comprobaciones rápidas
2. Pruebas
3. Seguridad
4. Empaquetado
5. Entorno real
El orden sigue una regla económica: lo barato y lo que más falla, primero. Un error de formato no debe esperar diez minutos de tests.
| Etapa | Qué detecta | Coste típico |
|---|---|---|
| Formato y lint | Estilo, errores evidentes, malas prácticas | Muy bajo |
| Comprobación de tipos | Incoherencias antes de ejecutar | Bajo |
| Escaneo de secretos | Credenciales subidas por error | Bajo |
| Tests unitarios | Lógica rota | Medio |
| Tests de integración | Fallos entre componentes, base de datos, colas | Alto |
| Análisis estático de seguridad (SAST) | Patrones vulnerables en el código propio | Medio |
| Auditoría de dependencias (SCA) | Librerías con vulnerabilidades conocidas y licencias incompatibles | Bajo |
| Build y empaquetado | Que el artefacto se genera correctamente | Medio |
| Pruebas end to end | Flujos completos desde la interfaz | Muy alto |
| Pruebas de humo tras desplegar | Que lo desplegado arranca y responde | Bajo |
Puertas de calidad
Una puerta de calidad es una condición automática que bloquea el avance. Para que funcionen, deben ser objetivas, automáticas y respetadas por todos, incluidas las personas con permisos de administración.
- Protección de la rama principal: exigir pull request, revisión aprobada y comprobaciones superadas antes de fusionar.
- Comprobaciones obligatorias: el merge solo se habilita si los jobs designados pasan.
- Cobertura sobre el código nuevo, mejor que un umbral global: un porcentaje global apenas se mueve y anima a escribir tests triviales.
- Rama actualizada: exigir que la rama esté al día con la principal evita fusionar algo que solo pasa contra una base antigua. Una cola de fusión (merge queue) automatiza esa comprobación.
- Revisión de código como puerta humana: lo que no se puede automatizar (diseño, intención, claridad).
Build reproducible y artefactos inmutables
El principio más valioso del continuo es construir una vez, desplegar muchas: el artefacto que se probó en staging es, byte a byte, el que llega a producción.
Mismo artefacto, distinta configuración
Cada paso exige las comprobaciones del anterior.
Para conseguirlo:
- Fijar las entradas: versiones exactas de dependencias con fichero de bloqueo (lockfile), imagen base con versión o digest, herramientas con versión concreta.
- Instalaciones deterministas: usar el comando que respeta el lockfile y falla si no cuadra, no el que resuelve versiones de nuevo.
- Identificar el artefacto por el commit y por su resumen criptográfico (digest), no solo por una etiqueta móvil como latest.
- Separar configuración y artefacto: lo que cambia entre entornos se inyecta al arrancar, nunca se hornea en el build.
- Generar una lista de materiales (SBOM) y una firma del artefacto, de modo que se pueda verificar su procedencia.
Un build reproducible en sentido estricto (dos compilaciones producen exactamente los mismos bytes) es un ideal exigente; en la práctica, la inmutabilidad del artefacto publicado y la trazabilidad hasta el commit ya dan la mayor parte del valor.
Entornos y promoción
Los entornos habituales son desarrollo, staging (o preproducción) y producción, y a veces entornos efímeros por pull request, que se crean al abrir el cambio y se destruyen al cerrarlo.
- Staging debería parecerse a producción en versiones, topología y configuración; si no, valida poco.
- La promoción mueve el mismo artefacto de un entorno al siguiente cuando se cumplen las condiciones: pruebas superadas, aprobación manual donde proceda, ventana horaria permitida.
- Los entornos protegidos de la plataforma permiten exigir revisores y limitar qué ramas pueden desplegar en ellos.
- Los datos de pruebas no deben ser datos reales de producción sin anonimizar.
Secretos y credenciales
Los secretos son el punto donde un pipeline cómodo se convierte en un riesgo grave.
| Práctica | Razón |
|---|---|
| Guardar secretos en el almacén de la plataforma o en un gestor externo, nunca en el repositorio | El historial de Git es permanente |
| Limitar cada secreto a un entorno o repositorio concreto | Reduce el alcance si se filtra |
| Preferir credenciales efímeras a claves de larga vida | Una clave robada caduca sola |
| No imprimir secretos ni variables de entorno completas | Los logs se leen y se conservan |
| Rotar y revocar ante cualquier sospecha | Un secreto expuesto se considera comprometido |
OIDC: adiós a las claves estáticas
Con OpenID Connect, el job pide a la plataforma un token firmado que dice quién es (repositorio, rama, entorno) y el proveedor cloud lo cambia por credenciales temporales. No hay clave almacenada que robar, y se puede restringir el acceso a una rama o entorno concretos.
Seguridad del propio pipeline
El pipeline tiene acceso a código, secretos y producción: es un objetivo atractivo para ataques a la cadena de suministro.
| Riesgo | Defensa |
|---|---|
| Permisos excesivos del token del workflow | Declarar permisos mínimos a nivel de workflow y ampliar solo en el job que lo necesita |
| Acciones de terceros que cambian bajo los pies | Fijarlas por hash completo de commit, no por una etiqueta que el autor puede mover |
| Pull requests de forks | No exponer secretos a código externo; extremar el cuidado con disparadores que ejecutan con privilegios sobre código ajeno |
| Inyección en scripts | No interpolar datos controlados por el atacante (título de PR, nombre de rama) directamente en comandos de shell; pasarlos como variables de entorno |
| Dependencias comprometidas | Lockfile, auditoría, y actualizaciones automatizadas revisadas |
| Cachés envenenadas | No restaurar cachés de ramas no confiables en builds de publicación |
| Runners persistentes | Efímeros y aislados |
| Artefactos manipulados | Firmar y verificar antes de desplegar |
Un workflow de ejemplo
Ejemplo de GitHub Actions con permisos mínimos, concurrencia, matriz, caché, artefacto y despliegue protegido. Las acciones se muestran con versión legible por claridad; en un proyecto real se fijarían por hash de commit.
name: ci
on:
pull_request:
push:
branches: [main]
permissions:
contents: read
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
jobs:
calidad:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm run lint
- run: npm run typecheck
test:
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
node: [20, 22]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
cache: npm
- run: npm ci
- run: npm test
build:
needs: [calidad, test]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm run build
- uses: actions/upload-artifact@v4
with:
name: dist
path: dist
desplegar:
if: github.ref == 'refs/heads/main'
needs: build
runs-on: ubuntu-latest
environment: produccion
permissions:
id-token: write
contents: read
steps:
- uses: actions/download-artifact@v4
with:
name: dist
- run: ./desplegar.shDetalles que merece la pena notar: los permisos globales son de solo lectura y solo el job de despliegue pide el token de identidad; la concurrencia cancela ejecuciones obsoletas de una misma rama; el despliegue depende del build y reutiliza su artefacto en lugar de recompilar; el entorno protegido puede exigir aprobación manual.
Pipelines rápidos
Un pipeline lento se esquiva. Como orientación general, cuanto más corto el ciclo de retroalimentación, más se respeta.
- Paralelismo: ejecutar en paralelo lo que no depende entre sí (lint, tipos y tests).
- Caché de dependencias y de builds, con claves derivadas del lockfile para que se invaliden cuando toca.
- Caché de capas de contenedor y builds incrementales.
- Test impact analysis: ejecutar solo los tests afectados por el cambio, con una ejecución completa periódica o en la rama principal.
- Particionar las suites largas entre varios runners.
- Cancelar ejecuciones obsoletas cuando llega un commit nuevo a la misma rama.
- Fallar pronto: las comprobaciones baratas delante.
- Vigilar la inestabilidad (flaky tests): un test que falla al azar enseña a reintentar en lugar de investigar. Se aísla, se arregla o se elimina.
Estrategias de ramas y su relación con el pipeline
| Estrategia | Idea | Encaje con CI/CD |
|---|---|---|
| Desarrollo basado en tronco | Ramas muy cortas que se integran a diario en la principal | El más natural para entrega continua; requiere banderas de funcionalidad y buenas pruebas |
| GitHub Flow | Rama por cambio, pull request y fusión a la principal, que siempre es desplegable | Sencillo y suficiente para muchos equipos |
| Gitflow | Ramas de desarrollo, release y hotfix de larga vida | Pensado para versiones empaquetadas con soporte paralelo; añade fricción si se despliega a menudo |
| Ramas de release | Una rama por versión mantenida | Útil con varias versiones en producción a la vez |
Las ramas de larga vida acumulan divergencia, y la divergencia es el riesgo que CI intenta eliminar.
GitOps
Compara de forma continua y corrige la deriva.
GitOps lleva la idea del control de versiones al despliegue: el estado deseado de la infraestructura y de las aplicaciones vive en un repositorio, y un agente dentro del entorno (Argo CD, Flux) lo compara con el estado real y lo reconcilia.
- Desplegar es fusionar un cambio en el repositorio de estado; volver atrás es revertir ese cambio.
- El pipeline de CI construye y publica la imagen y propone el cambio de versión; el agente hace el despliegue en modo pull, de modo que el pipeline no necesita credenciales del clúster.
- Se obtiene auditoría gratuita (quién cambió qué y cuándo) y detección de deriva (alguien tocó algo a mano).
Medir: las métricas DORA
Velocidad
Estabilidad
El programa de investigación DORA propone cuatro métricas para medir el rendimiento de la entrega de software:
| Métrica | Qué mide |
|---|---|
| Frecuencia de despliegue | Con qué regularidad se publica en producción |
| Tiempo de entrega de cambios | Desde que un cambio se confirma hasta que está en producción |
| Tasa de fallo de cambios | Qué proporción de despliegues provoca un fallo que exige intervención |
| Tiempo de recuperación | Cuánto se tarda en restablecer el servicio tras un fallo |
Dos miden velocidad y dos estabilidad, y deben leerse juntas: ir rápido rompiendo cosas o ir despacio sin romper nada son, ambos, resultados pobres. Cada equipo debería comparar su evolución con su propio pasado, no perseguir cifras ajenas.
Lista de comprobación
- Cada cambio pasa por un pull request con pipeline obligatorio.
- La rama principal está protegida y siempre es desplegable.
- El pipeline completo de un cambio normal tarda lo bastante poco como para esperarlo.
- Hay un único build por versión; los entornos solo difieren en configuración.
- Las dependencias están bloqueadas con lockfile y se auditan.
- Los permisos del workflow son mínimos y las acciones de terceros están fijadas por hash.
- No hay claves de larga vida en el almacén; se usan credenciales efímeras cuando es posible.
- El despliegue incluye una prueba de humo y una vía de vuelta atrás probada.
- Hay un procedimiento para los tests inestables.
- Se miden las cuatro métricas DORA y se revisan periódicamente.
Antipatrones
- Pipeline decorativo: existe, pero se puede saltar o se ignora cuando falla.
- Reintentar hasta que pase, en lugar de investigar la causa.
- Recompilar en cada entorno, con el riesgo de desplegar algo distinto de lo probado.
- Configuración horneada en la imagen, que obliga a construir de nuevo para cambiar un valor.
- Secretos en variables visibles o en el repositorio.
- Etiquetas móviles (latest, versiones mayores de acciones) como base de un build reproducible.
- Despliegues manuales «solo esta vez», que dejan el entorno fuera del control del código.
- Entornos de staging que no se parecen a producción.
- Ramas de larga vida que se integran de golpe antes de la entrega.
- Pipelines monolíticos de una hora que nadie espera.
- Cobertura como objetivo en sí misma, sin mirar la calidad de las pruebas.