Saltar al contenido
Under the Hood
Available in Spanish only

Cloud y DevOps

¿Sabes armar un buen pipeline de CI? Pasos y consideraciones

Última revisión:

General
  • CI/CD
  • Calidad
  • Buenas prácticas

Respuesta corta

Un proceso de CI corre en cada push o pull request y responde una pregunta: ¿este cambio se puede integrar a la rama principal sin romper nada? Para eso restaura dependencias, corre el lint, compila, ejecuta los tests y revisa vulnerabilidades, de lo más rápido a lo más lento.

Si todo pasa, genera un artefacto versionado y lo publica; si algo falla, se detiene y el PR queda bloqueado. Desplegar ese artefacto ya es CD.

En esta página

Explicación

Una pregunta en cada cambio

Pensemos en la cocina de un restaurante: cada vez que se hace un platillo, hay que probarlo y asegurarse de que los ingredientes sean los correctos. Por ejemplo, si una salsa quedó salada, se ajusta en el momento, no cuando el plato ya llegó a la mesa del cliente. Un pipeline de CI hace lo mismo con el código.

La integración continua consiste en integrar cambios pequeños a la rama principal varias veces al día, y que cada uno pase por una verificación automática. Esa verificación responde una sola pregunta: ¿este cambio se puede integrar sin romper nada? Mientras más rápido llegue la respuesta, más fácil es corregir. Martin Fowler lo explica a fondo en su artículo sobre integración continua.

Los pasos, en orden

El pipeline se dispara en cada push y en cada pull request, para que el error aparezca en el PR, antes de llegar a main, y no después del merge, cuando ya afecta a todo el equipo. Desde ahí, los pasos son:

  1. Descarga el código y restaura las dependencias. Con las versiones fijadas en el lockfile, para que el build sea reproducible, y con caché, para que sea rápido.
  2. Lint y análisis estático. Detectan errores de estilo y bugs evidentes en segundos, antes de gastar minutos compilando y probando.
  3. Build. Si el código no compila, nada de lo que sigue tiene sentido.
  4. Tests unitarios y cobertura. Tienen que pasar todos: un solo test en rojo detiene el pipeline. Lo que sí se mide en porcentaje es la cobertura (qué parte del código ejecutan los tests), y ahí se puede exigir un mínimo.
  5. Tests de integración. Prueban varias piezas juntas, por ejemplo contra una base de datos en un contenedor. Van después de los unitarios porque son más lentos y más pesados de levantar.
  6. Chequeo de seguridad. Busca dependencias con vulnerabilidades conocidas y secretos subidos por error. Toma poco tiempo y evita llevar un problema conocido a producción.
  7. Artefacto versionado y publicado. Genera la imagen, el paquete o el binario, lo etiqueta con el hash del commit para saber exactamente qué contiene y lo publica en un registro. Ese mismo artefacto es el que después se despliega en cada ambiente.

El criterio detrás del orden es fallar rápido: primero lo liviano y rápido, después lo pesado y lento. Si algo falla al principio, no se pierde tiempo en lo que viene.

Cuando un paso falla

  • El pipeline se detiene. No tiene sentido publicar un artefacto de un cambio que no pasó las validaciones.
  • El PR queda bloqueado. Con la rama protegida, no se puede mergear hasta que el pipeline esté en verde, así que la regla no depende de la disciplina de cada persona.
  • El autor recibe aviso. El feedback sirve más mientras el cambio todavía está fresco en su cabeza.
  • Si se rompe main, arreglarlo es la prioridad. Mientras esté en rojo, nadie puede integrar con confianza.

Dónde termina el CI

El CI termina cuando hay un artefacto validado y publicado. Llevarlo a los ambientes es trabajo del CD: la entrega continua lo deja listo para producción con una aprobación, y el despliegue continuo lo lleva solo.

Código

Un pipeline para un proyecto Node con TypeScript en GitHub Actions. GitLab CI, Azure Pipelines o Jenkins expresan los mismos pasos con otra sintaxis.

.github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
ci:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
services:
postgres:
image: postgres:18
env:
POSTGRES_PASSWORD: postgres # solo para el contenedor de pruebas
ports: ['5432:5432']
env:
IMAGE: ghcr.io/${{ github.repository }}:${{ github.sha }}
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v5
with:
node-version: 24
cache: npm
- run: npm ci
- run: npm run lint
- run: npm run build
- run: npm test -- --coverage # el umbral mínimo se define en la config de tests
- run: npm run test:integration
env:
DATABASE_URL: postgres://postgres:postgres@localhost:5432/postgres
- run: npm audit --audit-level=high
- run: docker build -t $IMAGE .
- if: github.ref == 'refs/heads/main' # en un PR se valida, pero no se publica
run: |
echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin
docker push $IMAGE

Trade-offs

  • Velocidad frente a confianza. Cada paso suma seguridad, pero también minutos. Si el pipeline pasa de unos 10 minutos, la gente deja de esperarlo, así que conviene cachear, paralelizar y sacar lo más lento (como los tests end-to-end) a un pipeline aparte.
  • La cobertura mínima es un piso, no una garantía. Sirve para que no baje, pero perseguir un porcentaje lleva a tests que ejecutan código sin comprobar nada.
  • Tests inestables. Relanzar hasta que pase es la salida fácil, pero el equipo deja de confiar en el rojo. Lo sano es arreglarlos o ponerlos en cuarentena.
  • Compilar una sola vez. El artefacto que se prueba es el que se despliega en todos los ambientes. Recompilar por ambiente rompe esa garantía.
  • Los errores típicos. Meter el deploy dentro del CI, correr el pipeline solo al hacer merge (y no en los PR) y escribir secretos en el YAML en vez de usar el gestor de secretos de la herramienta.

Entradas relacionadas

  • APIs

    API REST: no es solo mandar JSON, es cómo se manda

    REST es un estilo de arquitectura, no un estándar: los recursos se identifican con URLs y se manipulan con los verbos HTTP según su semántica, y cada respuesta trae un código de estado que dice qué pasó. Además es cliente-servidor, stateless y cacheable, lo que permite que cliente y servidor evolucionen por separado y que la API escale.

    General