DevOps

Pipelines de CI/CD

CI/CD convierte cada cambio en un artefacto inmutable y probado, y despliega ese mismo artefacto de la misma manera siempre. Un pipeline es simplemente una secuencia de trabajos que se ejecutan cada vez que el código se mueve.

intermediate15 min readUpdated 16 sept 2026
.github/workflows/ci.yml
yaml
# .github/workflows/ci.yml
name: ci

on:
  push:
    branches: [main]
  pull_request:

jobs:
  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 lint
      - run: npm run typecheck
      - run: npm test
Disparador
Push, pull request o tag
Runner
Una máquina limpia y desechable
Artefacto
Construido una vez, promovido a todas partes
Puertas
Lint, typecheck y tests
Entrega
Lanzamiento automatizado, aprobación manual
Despliegue
Automatizado hasta producción

Por que importa

Lo que te aporta un pipeline

Cada cambio es verificado

Cada push ejecuta los mismos pasos de build, lint, typecheck y test en un entorno limpio, para que el código roto se detecte durante la revisión y no después de un lanzamiento.

Un artefacto, muchos entornos

El pipeline construye la imagen o el bundle exactamente una vez y promueve esos mismos bytes a través de staging y producción, asegurando que lo que probaste sea lo que despliegas.

Los lanzamientos se vuelven rutinarios

Los despliegues son automatizados, observables y reversibles. Hacer un rollback significa redesplegar el tag anterior, no reconstruir desde una rama antigua bajo presión.

La imagen completa

Las tres ideas detrás de CI/CD

Un evento dispara el trabajo, un runner desechable lo ejecuta y un artefacto inmutable es lo que se mueve entre entornos.

El disparador

Reaccionar

Un push, un pull request o un tag inicia el pipeline. El evento decide qué trabajos se ejecutan y a qué entorno se dirigen.

El runner

Ejecutar

Una máquina desechable descarga el código y ejecuta cada paso. Nada sobrevive entre trabajos, lo que hace que el resultado sea reproducible.

El artefacto

Enviar

El build produce una salida inmutable, como una imagen de contenedor o un bundle, que se promueve sin cambios a través de cada entorno.

HTML5 de un vistazo

Las piezas móviles de un pipeline

Disparadores

on push, pull_request o tag deciden cuándo comienza un pipeline.

Jobs y steps

Un job se ejecuta en un runner; sus steps se ejecutan en orden y fallan rápido.

Artefactos

La salida del build se almacena una vez y es descargada por jobs posteriores.

Caching

Las cachés de dependencias y de build reducen minutos en cada ejecución.

Matrices

Ejecuta el mismo job en diferentes versiones de Node.js y sistemas operativos.

Secretos

Las credenciales se inyectan en tiempo de ejecución y se ocultan en los logs.

Flujo

Qué hace un pipeline en cada push

La misma estructura se aplica ya sea que el pipeline tenga diez líneas o cien, y ya sea que despliegue en una VM o en un cluster de Kubernetes.

  1. 1

    Disparador en push o pull request

    La plataforma envía un evento con el commit, la rama y el actor, y el pipeline selecciona los jobs correspondientes.

  2. 2

    Descarga del código

    Un runner limpio clona el commit exacto, para que nada de una ejecución anterior se filtre en esta.

  3. 3

    Restaurar cachés

    Las cachés de dependencias y de build se restauran mediante el hash del lockfile, convirtiendo una instalación fría en una caliente.

  4. 4

    Instalar dependencias

    Una instalación basada en lockfile fija el árbol de dependencias para que cada ejecución y cada máquina resuelvan las mismas versiones.

  5. 5

    Lint, typecheck y test

    Las verificaciones rápidas se ejecutan primero y fallan el pipeline tempranamente. Una puerta roja evita que el artefacto llegue a construirse.

  6. 6

    Construir el artefacto o imagen

    La salida se construye una vez y se sube o se envía a un registro, etiquetada con el commit para que sea rastreable.

  7. 7

    Desplegar en la rama main

    Solo los merges a la rama protegida despliegan, usualmente detrás de un entorno con sus propios secretos y aprobaciones.

  8. 8

    Ejecutar un smoke check

    Una solicitud rápida contra el nuevo lanzamiento confirma que está sirviendo antes de desviar todo el tráfico.

  9. 9

    Reportar estado

    El commit recibe una marca de aprobado o fallido, y un despliegue fallido se revierte o se deja con la versión anterior sirviendo.

La guia completa

Pipelines de CI/CD: Todo lo que necesitas saber

Qué significan realmente CI y CD

La integración continua (Continuous integration) es la práctica de fusionar el trabajo de cada desarrollador en una rama compartida con frecuencia y verificarlo automáticamente. Cada fusión dispara un build, un paso de lint y la suite de pruebas en una máquina donde nadie desarrolla. El objetivo es encontrar problemas de integración en cuestión de minutos, mientras el cambio aún es pequeño, en lugar de enfrentarse a un merge doloroso al final de una rama larga.

La entrega continua (Continuous delivery) extiende esa idea: la rama principal está siempre en un estado apto para su lanzamiento, y el pipeline puede desplegarla a producción en cualquier momento. El artefacto se construye y se prueba con cada cambio; un humano decide cuándo realizar el lanzamiento. El despliegue continuo (Continuous deployment) elimina esa decisión humana, enviando cada cambio que supere los filtros directamente a producción.

La diferencia entre delivery y deployment es la puerta de aprobación, y es la única diferencia. Todo lo anterior —el build, las pruebas, el artefacto— es idéntico.

A menudo, los equipos afirman “hacer CI/CD” cuando no hacen ninguna de las dos cosas. La integración continua requiere que el build se ejecute en una máquina compartida y autoritativa, no solo en una laptop antes de un commit. La entrega continua requiere que el artefacto producido por el pipeline sea el que se envíe, no algo reconstruido a mano en el momento del lanzamiento. Si falta cualquiera de las dos, el ciclo queda abierto y los beneficios se pierden.

La anatomía de un pipeline

Todo pipeline, desde un archivo de GitHub Actions de diez líneas hasta una configuración empresarial de mil jobs, se construye a partir del mismo vocabulario.

  • Trigger — el evento que inicia una ejecución: un push, un pull request, un tag, una programación o un dispatch manual.
  • Workflow — el archivo que declara los triggers y los jobs. En GitHub Actions se encuentra en .github/workflows/.
  • Job — una unidad de trabajo que se ejecuta en un runner. Los jobs se ejecutan en paralelo a menos que declares dependencias con needs.
  • Step — un comando único o una acción reutilizable dentro de un job. Los steps se ejecutan en orden y el job se detiene ante el primer fallo.
  • Runner — la máquina que ejecuta un job. Los hosted runners son desechables; los self-hosted runners son máquinas que tú gestionas.
  • Artifact — un archivo producido por un job y almacenado para jobs posteriores o para humanos: un binario, un bundle, un reporte de tests.
  • Cache — un directorio que se restaura entre ejecuciones para evitar repetir tareas costosas, como la instalación de dependencias.
  • Environment — un objetivo nombrado, como staging o production, con sus propios secrets, reglas de protección y URL.
  • Secret — un valor cifrado que se inyecta en un job en tiempo de ejecución y se oculta en los logs.
  • Status check — el resultado de aprobado o fallido reportado al commit, el cual puede ser requerido por la protección de ramas (branch protection).

Una vez que estos conceptos están claros, cualquier sistema de CI se vuelve legible. Jenkins, GitLab CI, CircleCI y GitHub Actions utilizan los mismos conceptos, aunque con nombres diferentes.

Un workflow de GitHub Actions, línea por línea

El workflow más sencillo y útil es aquel que descarga el código, instala las dependencias y ejecuta las pruebas.

name: ci

on:
  push:
    branches: [main]
  pull_request:

jobs:
  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 test

on declara los triggers. Tanto un push a main como cualquier pull request inician una ejecución. jobs.test se ejecuta en un runner de ubuntu-latest limpio. uses ejecuta una acción reutilizable; run ejecuta un comando de shell. npm ci instala exactamente lo que indica el lockfile, lo que hace que la ejecución sea reproducible. cache: npm indica a la acción de configuración que cachee el directorio de npm, utilizando el lockfile como clave.

Añade concurrency para cancelar las ejecuciones superadas, lo que ahorra tiempo y dinero en las ramas activas:

concurrency:
  group: ci-${{ github.ref }}
  cancel-in-progress: true

Cuando un desarrollador hace push de tres commits seguidos, solo sobrevive la ejecución más reciente. Las anteriores se cancelan y el pull request muestra un único estado actual en lugar de una cola de estados obsoletos.

Construye una vez, despliega el mismo artefacto

El hábito más valioso en un pipeline es construir exactamente una vez. El pipeline compila, prueba y empaqueta la aplicación una sola vez, y cada entorno, desde staging hasta producción, ejecuta ese mismo resultado.

La alternativa —volver a construir para cada entorno— parece inofensiva, pero no lo es. Diferentes builds pueden resolver versiones de dependencias distintas, tomar diferentes digests de imágenes base o embeber un NODE_ENV diferente. Entonces, staging prueba un binario que producción nunca ejecutará, y que “pasó en staging” no demuestra nada.

Dos reglas hacen que el “build-once” sea práctico. Primero, etiqueta el artefacto por commit, nunca con un nombre mutable como latest:

docker build -t ghcr.io/me/app:$GIT_SHA .
docker push ghcr.io/me/app:$GIT_SHA

Segundo, inyecta la configuración en tiempo de ejecución. La imagen no debe saber si se está ejecutando en staging o en producción. Las URLs de la base de datos, los feature flags y las API keys llegan como variables de entorno cuando el contenedor inicia. El build es idéntico en todas partes; solo difiere el entorno.

Para promover, haz referencia al digest inmutable en lugar de a la etiqueta, para que incluso una imagen re-etiquetada no pueda variar:

kubectl set image deploy/app \
  app=ghcr.io/me/app@sha256:...

Esta es la base que hace que los rollbacks sean triviales. El release anterior es simplemente el digest anterior, que sigue estando en el registry, listo para desplegarse en segundos.

Tests, lint y typecheck como puertas de enlace

Una puerta de enlace (gate) es una verificación que debe superarse antes de que el pipeline continúe. Las puertas esenciales son rápidas y económicas: linting, type checking y la suite de pruebas unitarias. Se ejecutan en cada pull request y evitan que el artefacto se construya en caso de fallo.

Ordénalas de la más rápida a la más lenta. El lint y el typecheck suelen terminar en segundos y detectan una gran cantidad de errores. Las pruebas unitarias van después. Las pruebas de integración y end-to-end, que son más lentas, pueden ejecutarse en paralelo o únicamente en la rama principal.

jobs:
  lint:
    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 lint
      - run: npm run typecheck

  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 test

Dos jobs se ejecutan en paralelo, cada uno en su propio runner. El lint falla en veinte segundos sin tener que esperar a la suite de pruebas. La contrapartida es que ambos jobs instalan las dependencias; un cache compartido hace que este proceso sea económico.

Las puertas de enlace solo son útiles si son obligatorias. En la plataforma de forja, marca las verificaciones como requeridas en la protección de ramas (branch protection) para que un pull request no pueda fusionarse mientras alguna de ellas esté en rojo.

Matrices y paralelismo

Una matriz ejecuta una definición de trabajo a través de varias combinaciones de entradas. Es la forma de probar múltiples versiones de Node y sistemas operativos sin duplicar el YAML.

jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, macos-latest]
        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

Esto se expande a cuatro trabajos. fail-fast: false permite que los demás terminen incluso si uno falla, lo cual es útil cuando quieres saber si el fallo es específico de una versión.

El paralelismo no es gratuito. Cada trabajo aprovisiona un runner e instala las dependencias, por lo que una matriz amplia cuesta más tiempo y dinero que una estrecha. Prueba las versiones que realmente soportas, no todas las versiones que existen.

Para suites de pruebas grandes, fragmenta (shard) las pruebas mismas: pasa un índice de fragmento y el total al test runner para que cada uno de los cuatro trabajos ejecute una cuarta parte de las pruebas. La suite termina en aproximadamente una cuarta parte del tiempo.

Secretos y variables de entorno

Los secretos son valores cifrados que el pipeline inyecta en tiempo de ejecución. Deben residir en el almacén de secretos de la plataforma, nunca en el repositorio y nunca en la imagen.

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - run: ./deploy.sh
        env:
          DATABASE_URL: ${{ secrets.DATABASE_URL }}
          API_KEY: ${{ secrets.API_KEY }}

La plataforma oculta los valores de los secretos conocidos en los logs, pero este proceso es “best-effort”. No puede detectar un secreto que haya sido transformado, codificado en base64 o impreso por una herramienta que reformatee el texto. La regla de oro es sencilla: nunca imprimas un secreto. No ejecutes env en un paso de depuración, no hagas echo de un token y no pases un secreto a un comando que registre sus argumentos en los logs.

Asigna los secretos al entorno que los necesite. Un despliegue de staging no tiene motivos para tener la contraseña de la base de datos de producción. En GitHub, los secretos de entorno solo están disponibles para los jobs que declaran dicho entorno y pueden requerir una aprobación previa.

Para los proveedores de nube, prefiere OIDC sobre las claves de larga duración. El pipeline intercambia un token de identidad de corta duración por credenciales temporales, por lo que no hay un secreto estático que pueda filtrarse o que requiera rotación.

permissions:
  id-token: write
  contents: read

steps:
  - uses: aws-actions/configure-aws-credentials@v4
    with:
      role-to-assume: arn:aws:iam::123456789012:role/deploy
      aws-region: eu-west-1

El token es válido durante unos minutos y está limitado a un solo rol. Si se filtra, el radio de impacto se limita a una única ejecución.

Una advertencia merece especial énfasis: los pull requests de forks ejecutan código no confiable. Nunca expongas secretos a ese código. Usa pull_request en lugar de pull_request_target para cualquier proceso que descargue y compile código de colaboradores, y requiere aprobación antes de ejecutar workflows de colaboradores que contribuyen por primera vez.

Entornos, aprobaciones y ramas protegidas

Un environment es un objetivo de despliegue nombrado que posee sus propios secrets, reglas de protección y URL. Es el mecanismo que mantiene la producción separada de todo lo demás.

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment:
      name: production
      url: https://app.example.com
    steps:
      - run: ./deploy.sh

Los environments pueden requerir una aprobación manual antes de que se ejecute un job, restringir qué ramas pueden desplegar en ellos y limitar cuánto tiempo espera un despliegue antes de expirar por timeout. Aquí es donde reside el “botón humano” de la entrega continua: el pipeline se ejecuta automáticamente hasta llegar a la puerta de enlace, y un revisor lo libera.

Las ramas protegidas complementan a los environments. En la rama main, se puede requerir un pull request, requerir que se superen los status checks, requerir una revisión y prohibir los force pushes. Juntos, esto garantiza que la única vía hacia producción sea a través del pipeline, que es precisamente el objetivo.

Estrategias de despliegue y rollbacks

La forma en que una versión nueva reemplaza a una antigua es una decisión de diseño con consecuencias reales para los usuarios.

El Rolling deployment reemplaza las instancias de pocas en pocas. La capacidad disminuye ligeramente durante el intercambio, pero no se requiere infraestructura adicional. Es la opción predeterminada en la mayoría de las plataformas y el punto de partida ideal.

El Blue-green deployment ejecuta dos entornos completos. El tráfico se dirige al entorno “blue” mientras que el “green” recibe la nueva versión; una vez que el entorno “green” está saludable, un único cambio de enrutamiento redirige todo el tráfico. El rollback consiste en volver al entorno anterior, lo cual es casi instantáneo. El coste es ejecutar dos entornos simultáneamente.

El Canary deployment envía una pequeña fracción del tráfico —un uno por ciento, luego cinco, luego cincuenta— a la nueva versión y monitoriza las tasas de error y la latencia antes de continuar. Esto permite detectar problemas que un health check no puede identificar, a cambio de un enrutamiento y una monitorización más complejos.

Independientemente de la estrategia, el despliegue debe ser reversible. Dado que el artefacto anterior es inmutable y aún reside en el registro, un rollback es un redespiegue del digest anterior, no una reconstrucción:

kubectl rollout undo deploy/app

Dos detalles hacen que los rollbacks sean seguros. Las migraciones de base de datos deben ser compatibles hacia atrás durante al menos una versión, para que el código antiguo pueda seguir funcionando con el nuevo esquema. Además, los feature flags permiten desactivar comportamientos nuevos sin necesidad de desplegar nada, lo cual es el rollback más rápido que existe.

Registros de artefactos e imágenes de contenedores

Un registro de artefactos almacena el resultado de la compilación: imágenes de contenedores, paquetes de npm, binarios, tarballs. Es el punto de entrega entre la fase de construcción y la de despliegue, y debe ser inmutable.

Las imágenes de contenedores son el artefacto más común porque incluyen su propio runtime. Un registro como GitHub Container Registry, Amazon ECR o Docker Hub almacena capas, y el pipeline realiza un push hacia él una vez por cada build. Posteriormente, los despliegues descargan el digest exacto.

- uses: docker/build-push-action@v6
  with:
    context: .
    push: true
    tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
    cache-from: type=gha
    cache-to: type=gha,mode=max

Las opciones de cache-from y cache-to mantienen la caché de capas de Docker entre ejecuciones, evitando que una capa de dependencias que no ha cambiado se reconstruya desde cero cada vez.

Mantén una política de retención. Los registros acumulan gigabytes rápidamente, y eliminar imágenes antiguas es fundamental para mantener el pipeline rápido. Conserva al menos las últimas versiones para que el rollback siga siendo posible.

Verificaciones de estado y protección de ramas

Una verificación de estado (status check) es el veredicto del pipeline sobre un commit. La plataforma lo adjunta al pull request, y la protección de ramas decide si es meramente informativo o obligatorio.

Configura la rama main para requerir:

  • Un pull request antes de fusionar, con al menos una aprobación.
  • Verificaciones de estado aprobadas, incluyendo lint, typecheck y tests.
  • Que la rama esté actualizada con main antes de fusionar, para que las verificaciones se ejecuten sobre el código que realmente se implementará.
  • Prohibir force pushes y eliminaciones.

El resultado es que la rama main siempre está en verde. Cada commit en ella ha pasado por los mismos filtros, y cada despliegue desde ella es un artefacto cuya calidad está garantizada.

Mantén la lista de requisitos corta. Exigir una suite de end-to-end lenta en cada pull request hace que los desarrolladores tengan que esperar; exigirlo en la cola de fusión (merge queue) o en la rama main ofrece la misma seguridad sin generar fricción.

Caching y builds incrementales

El caching es la diferencia entre un pipeline de dos minutos y uno de doce. El principio fundamental es no repetir nunca un trabajo cuyos inputs no hayan cambiado.

El cache más valioso es el de las dependencias. Utiliza el hash del lockfile como clave para que se invalide exactamente cuando las dependencias cambien:

- uses: actions/cache@v4
  with:
    path: ~/.npm
    key: npm-${{ hashFiles('**/package-lock.json') }}
    restore-keys: npm-

restore-keys permite una coincidencia parcial: incluso cuando no hay una coincidencia exacta de la clave, se restaura el cache más reciente y se actualiza incrementalmente, lo cual es mucho más rápido que una instalación desde cero.

Las herramientas de build añaden sus propios caches. El build incremental de TypeScript, el pre-bundling de dependencias de Vite y el layer cache de Docker se benefician de ser persistidos entre ejecuciones. Para Docker, utiliza el registry o el backend de cache del proveedor de CI en lugar del daemon local, el cual se descarta junto con el runner.

Un cache es una optimización, nunca una fuente de verdad. Si un cache está corrupto o desactualizado, la ejecución debe seguir siendo correcta. Las herramientas de build que confían ciegamente en un cache pueden producir resultados erróneos, por lo que es recomendable definir las claves de los caches de forma conservadora y tratar un cache miss como algo normal.

Pipelines para monorepos

Un monorepo contiene muchos paquetes en un solo repositorio. Un pipeline ingenuo reconstruye y vuelve a probar todos ellos en cada cambio, lo que se vuelve más lento a medida que el repositorio crece.

La solución es la detección de cambios. Los filtros de rutas y los grafos de dependencias indican al pipeline qué paquetes se ven afectados por un diff, y solo esos se compilan.

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: pnpm install --frozen-lockfile
      - run: pnpm turbo run test --filter='...[origin/main]'

El filtro selecciona los paquetes modificados desde main más todo aquello que dependa de ellos. Un cambio en una utilidad compartida activa sus dependientes; un cambio en un paquete hoja solo se activa a sí mismo.

Añade un remote cache para que el resultado de la compilación y los resultados de las pruebas se compartan entre máquinas y ejecuciones. Si las entradas de un paquete no han cambiado, su build se restaura en lugar de volver a computarse. En un monorepo, esto suele representar una ganancia mayor que el almacenamiento en caché de dependencias.

Mantén el pipeline honesto: un cambio en el lockfile de la raíz o en la configuración compartida aún debería ejecutar la suite completa. Las pruebas selectivas son una optimización que nunca debe permitir que un paquete roto pase desapercibido.

Seguridad del pipeline

El pipeline contiene credenciales de producción, lo que lo convierte en un objetivo de alto valor. Trata los archivos de workflow como código sensible a la seguridad y revisa cuidadosamente cualquier cambio en ellos.

Fija las acciones a una versión. Una etiqueta como @v4 es un puntero móvil; un commit SHA completo es inmutable. Fijar a un SHA es la opción más estricta y evita que una etiqueta comprometida ejecute código malicioso en tu pipeline.

Otorga el privilegio mínimo. Configura permissions explícitamente en cada workflow y job, estableciendo el acceso de solo lectura por defecto, y añade permisos de escritura solo donde sea necesario.

permissions:
  contents: read

El GITHUB_TOKEN predeterminado suele ser más amplio de lo que cualquier job necesita. Restringirlo significa que un paso comprometido no podrá hacer push de commits ni publicar paquetes.

Prefiere OIDC sobre claves estáticas, como se describió anteriormente. Las credenciales de corta duración limitadas a un solo rol eliminan el secreto persistente que los atacantes más desean.

No ejecutes código no confiable con secretos. Los pull requests desde forks son el vector clásico. Usa pull_request para la compilación y las pruebas, requiere aprobación para los colaboradores primerizos y nunca descargues ni ejecutes código de un fork en un workflow que tenga acceso a secretos.

Revisa las acciones de terceros. Una acción es una dependencia que se ejecuta con tus credenciales. Prefiere acciones del propio forge o de editores conocidos, y audita el resto.

Mejores prácticas

  • Construye el artefacto una sola vez y promociónalo a través de cada entorno.
  • Etiqueta los artefactos por commit SHA o digest, nunca solo por latest.
  • Inyecta la configuración en tiempo de ejecución; mantén la construcción idéntica en todas partes.
  • Ejecuta el lint y el typecheck antes de la suite de pruebas más lenta para que los fallos se detecten rápido.
  • Haz que las comprobaciones importantes sean obligatorias en la protección de ramas.
  • Almacena en caché las dependencias usando como clave el hash del lockfile, con restore keys.
  • Limita los secretos a entornos específicos y prefiere credenciales OIDC de corta duración.
  • Nunca imprimas secretos y nunca los expongas a pull requests no confiables.
  • Fija las versiones de las acciones de terceros y establece permisos de token de mínimo privilegio.
  • Usa concurrency para cancelar ejecuciones superadas.
  • Mantén el pipeline rápido; si es lento, se terminará ignorando.
  • Haz que los rollbacks sean un redespiegue del digest anterior.

Errores comunes

  • Reconstruir la imagen de forma distinta para cada entorno y llamar a eso “promoción”.
  • Incrustar la configuración del entorno en la imagen durante el tiempo de compilación.
  • Usar latest como único tag y perder la trazabilidad.
  • Imprimir secretos en un paso de depuración asumiendo que el enmascaramiento lo detecta todo.
  • Exponer secretos a workflows activados por pull requests de forks.
  • Referenciar actions de terceros mediante una rama inestable.
  • Dejar los permisos del token predeterminado totalmente abiertos.
  • Marcar cada check opcional como obligatorio, haciendo que los merges sean lentísimos.
  • Permitir que los tests flaky permanezcan en rojo hasta que nadie lea los resultados.
  • Implementar un caching agresivo sin una clave que se invalide ante cambios reales en el input.
  • Reconstruir todo en un monorepo ante cualquier cambio.
  • No tener una ruta de rollback más allá de revertir un commit y esperar.

Próximos pasos

Un pipeline finaliza desplegando un artefacto, por lo que el siguiente paso natural son las Cloud Platforms, donde ocurren realmente el release, los health checks y los rollbacks. El artefacto suele ser un contenedor, y la guía de Docker cubre las imágenes y los multi-stage builds que produce el pipeline. La mayoría de los runners son máquinas Linux, por lo que la guía de Linux explica la shell y las herramientas de las que dependen tus pasos, mientras que Node.js sienta las bases del runtime y la toolchain que se está construyendo.

En la practica

Cuatro jobs, un pipeline

Integración continua, una matriz de tests, la construcción de una imagen y un despliegue controlado: la estructura completa de un pipeline de producción.

.github/workflows/ci.yml
name: ci

on:
  push:
    branches: [main]
  pull_request:

concurrency:
  group: ci-${{ github.ref }}
  cancel-in-progress: true

jobs:
  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 lint
      - run: npm run typecheck
      - run: npm test

Construir una vez, promover el artefacto

Reconstruir por entorno significa que staging y producción ejecutan binarios diferentes construidos desde la misma fuente. Construye una vez, etiqueta por commit y promueve ese artefacto exacto.

Preferir
# build once, tagged by the commit
docker build -t ghcr.io/me/app:$GIT_SHA .
docker push ghcr.io/me/app:$GIT_SHA

# every environment runs the same digest
kubectl set image deploy/app \
  app=ghcr.io/me/app@sha256:...
Evitar
# rebuild separately for each environment
docker build -t app:staging \
  --build-arg NODE_ENV=staging .
docker build -t app:prod \
  --build-arg NODE_ENV=production .

# staging and prod are different binaries

Versiones de acciones fijadas

Una acción referenciada por una rama móvil puede cambiar en cualquier momento, lo que representa un riesgo tanto de fiabilidad como de cadena de suministro.

Preferir
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: docker/build-push-action@v6
Evitar
- uses: actions/checkout@master
- uses: some-org/some-action@main
# runs whatever the branch points at today

Compromisos

¿Vale la pena la configuración de un pipeline completo?

Un pipeline es código que debes mantener. Para cualquier proyecto con más de un colaborador, se amortiza casi inmediatamente.

Strengths

  • El feedback llega temprano

    Un build roto es una corrección de cinco minutos en un pull request, no un rollback a medianoche. Cuanto antes se detecte un defecto, más barato es solucionarlo.

  • Los lanzamientos son repetibles

    Los mismos pasos se ejecutan siempre, por lo que el despliegue deja de depender de quién esté al teclado y qué estado local tenga.

  • El artefacto es rastreable

    Un commit mapea a un digest de imagen, que mapea a un despliegue activo. Responder "qué hay en producción" se convierte en una búsqueda, no en una investigación.

Trade-offs

  • Los pipelines lentos se ignoran

    Cuando una ejecución tarda treinta minutos, la gente deja de vigilarla y empieza a mergear aunque esté en rojo. Mantén las puertas rápidas y ejecuta las costosas en paralelo.

  • Los secretos son una superficie de ataque

    Cualquier flujo de trabajo que ejecute código no confiable puede ser engañado para filtrar credenciales. Limita el alcance de los tokens, usa credenciales OIDC de corta duración y nunca expongas secretos a pull requests de forks.

  • Los tests inestables (flaky) envenenan la confianza

    Un test que falla una de cada diez veces enseña al equipo a presionar 'reintentar' en lugar de leer el error. Pon en cuarentena los tests inestables en lugar de tolerarlos.

Preguntas frecuentes

Preguntas frecuentes

Keep learning

Related topics from the roadmap.

$ comienza a aprender

Listo para aprender CI/CD Pipelines?

Nuestro tutorial interactivo te guia a traves de CI/CD Pipelines paso a paso — con quizzes y codigo real que puedes ejecutar en el navegador.