¿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:
- 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.
- Lint y análisis estático. Detectan errores de estilo y bugs evidentes en segundos, antes de gastar minutos compilando y probando.
- Build. Si el código no compila, nada de lo que sigue tiene sentido.
- 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.
- 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.
- 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.
- 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.
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 $IMAGETrade-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