DevOps from Zero to Hero: Tu Primer Pipeline de CI con GitHub Actions

2026-05-03 | Gabriel Garrido | 18 min de lectura
Share:

Apoya este blog

Si te resulta util este contenido, considera apoyar el blog.

Introduccion

Bienvenido al quinto articulo de la serie DevOps from Zero to Hero. En el articulo anterior escribimos tests unitarios y de integracion para un proyecto TypeScript. Los tests son geniales, pero solo sirven si alguien los corre. Ese alguien no deberias ser vos, manualmente, justo antes de un deploy. Deberia ser una maquina que los corre cada vez que el codigo cambia.


De eso se trata la Integracion Continua (CI): automatizar las tareas aburridas, repetitivas y criticas para que los humanos puedan enfocarse en escribir codigo. En este articulo vamos a construir un pipeline de CI completo con GitHub Actions desde cero. Al final, cada push y pull request a tu repositorio va a hacer lint del codigo automaticamente, correr tests, construir una imagen Docker y pushearla a un registro de contenedores.


Vamos a meternos de lleno.


Que es CI y por que importa

Integracion Continua es la practica de construir y testear codigo automaticamente cada vez que alguien pushea un cambio. La palabra “continua” es importante: esto no es algo que haces una vez por semana o antes de un release. Pasa en cada commit, cada pull request, cada vez.


Por que importa? Tres razones:


  • Atrapar bugs temprano: Un bug encontrado en CI cuesta minutos en arreglar. Un bug encontrado en produccion cuesta horas, confianza de los clientes y a veces plata. Cuanto antes lo atrapes, mas barato es.
  • Aplicar estandares: Linting, formateo y type checking no deberian depender de que los desarrolladores se acuerden de correrlos. CI aplica estos estandares automaticamente, cada vez.
  • Automatizar tareas repetitivas: Construir imagenes Docker, correr suites de tests, generar artefactos. Estas son cosas que deberia hacer una maquina, no una persona.

Sin CI, tu workflow se ve asi: un desarrollador escribe codigo, se olvida de correr el linter, pushea a main, rompe el build y todo el equipo se entera una hora despues. Con CI, el linter corre automaticamente, el push se bloquea y el desarrollador lo arregla en cinco minutos antes de que afecte a alguien mas.


CI es la primera capa real de automatizacion en un pipeline de DevOps. Todo lo demas, delivery continuo, deployment continuo, infraestructura como codigo, todo se construye arriba de CI.


Fundamentos de GitHub Actions

GitHub Actions es una plataforma de CI/CD integrada en GitHub. Definis workflows como archivos YAML en un directorio .github/workflows/, y GitHub los ejecuta por vos en maquinas virtuales hosteadas. No hay un servicio separado que configurar, no hay webhooks que armar y no hay servidores que administrar.


Antes de escribir YAML, entendamos los conceptos clave:


  • Workflow: Un archivo YAML que define un proceso automatizado. Cada workflow vive en .github/workflows/ y se dispara por eventos.
  • Evento (trigger): Lo que causa que el workflow se ejecute. Triggers comunes son push, pull_request y schedule.
  • Job: Un conjunto de pasos que corren en la misma maquina virtual (llamada “runner”). Un workflow puede tener multiples jobs, y por defecto corren en paralelo.
  • Step: Una tarea individual dentro de un job. Un step puede ejecutar un comando de shell o usar una action pre-construida.
  • Action: Una unidad reutilizable de codigo que realiza una tarea comun. Por ejemplo, actions/checkout@v4 clona tu repositorio, y actions/setup-node@v4 instala Node.js.
  • Runner: La maquina virtual que ejecuta tu job. GitHub provee runners hosteados con Ubuntu, Windows y macOS.

Aca esta la jerarquia visualizada:


Workflow (.github/workflows/ci.yml)
  ├── Evento: push a main, pull_request
  ├── Job: lint
  │     ├── Step: Checkout codigo
  │     ├── Step: Setup Node.js
  │     └── Step: Correr ESLint
  ├── Job: test
  │     ├── Step: Checkout codigo
  │     ├── Step: Setup Node.js
  │     ├── Step: Instalar dependencias
  │     └── Step: Correr Vitest
  └── Job: build
        ├── Step: Checkout codigo
        ├── Step: Setup Docker Buildx
        └── Step: Build y push imagen

Triggers: cuando corre CI?

La key on en tu archivo de workflow define cuando se ejecuta. Estos son los triggers que vas a usar con mas frecuencia:


# Correr en cada push a main
on:
  push:
    branches: [main]

# Correr en cada pull request apuntando a main
on:
  pull_request:
    branches: [main]

# Correr en push y pull request
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

# Correr en un schedule (sintaxis cron, todos los dias a las 6 AM UTC)
on:
  schedule:
    - cron: "0 6 * * *"

# Correr manualmente desde la UI de GitHub
on:
  workflow_dispatch:

Para un proyecto tipico, queres que CI corra tanto en push como en pull_request a la rama main. El trigger de push atrapa cualquier cosa que llegue a main directamente, y el trigger de pull request te da feedback antes de mergear.


Construyendo el pipeline paso a paso

Vamos a construir un pipeline de CI real para un proyecto TypeScript. Empezamos simple y vamos agregando funcionalidades de forma incremental. Crea el archivo .github/workflows/ci.yml en tu repositorio:


# .github/workflows/ci.yml
name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  lint:
    name: Lint
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: "22"
          cache: "npm"

      - name: Install dependencies
        run: npm ci

      - name: Run ESLint
        run: npx eslint . --max-warnings 0

  test:
    name: Test
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: "22"
          cache: "npm"

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm test

      - name: Run tests with coverage
        run: npm run test:coverage

      - name: Upload coverage report
        uses: actions/upload-artifact@v4
        with:
          name: coverage-report
          path: coverage/

Veamos que esta pasando aca:


  • actions/checkout@v4: Clona tu repositorio en el runner. Sin esto, el runner no tiene codigo con el que trabajar.
  • actions/setup-node@v4: Instala la version especificada de Node.js y configura el caching de npm.
  • npm ci: Instala las dependencias de package-lock.json exactamente como se especifican. A diferencia de npm install, no modifica el lockfile y es mas rapido y confiable en CI.
  • npx eslint . --max-warnings 0: Corre ESLint y falla si hay algun warning. Esto es mas estricto que el comportamiento por defecto, que solo falla por errores. Tratar los warnings como errores en CI evita que se acumulen.
  • Los jobs de lint y test corren en paralelo: Como no dependen uno del otro, GitHub los corre al mismo tiempo, haciendo tu pipeline mas rapido.

Agregando el build de Docker

Ahora agreguemos un job que construya una imagen Docker y la pushee a GitHub Container Registry (GHCR). Este job solo deberia correr despues de que lint y tests pasen, asi que usamos la keyword needs para crear una dependencia:


  build:
    name: Build and Push Docker Image
    runs-on: ubuntu-latest
    needs: [lint, test]
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'

    permissions:
      contents: read
      packages: write

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Log in to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ghcr.io/${{ github.repository }}
          tags: |
            type=sha,prefix=
            type=raw,value=latest

      - name: Build and push
        uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

Hay mucho pasando aca, asi que vamos a desarmarlo:


  • needs: [lint, test]: Este job espera a que tanto lint como test pasen antes de correr. Si alguno falla, el build se saltea completamente.
  • if: github.event_name == 'push' && github.ref == 'refs/heads/main': Solo construye imagenes en pushes a main, no en pull requests. No queres pushear una imagen Docker por cada PR.
  • permissions: GitHub Actions usa un GITHUB_TOKEN que se crea automaticamente para cada ejecucion de workflow. Necesitamos packages: write para pushear a GHCR.
  • docker/setup-buildx-action@v3: Configura Docker Buildx, que es una herramienta de build extendida que soporta funcionalidades avanzadas como caching y builds multi-plataforma.
  • docker/login-action@v3: Se loguea a GHCR usando el GITHUB_TOKEN integrado. No necesitas crear un token de acceso personal.
  • docker/metadata-action@v5: Genera tags y labels automaticamente. Tagueamos con el SHA de Git (para trazabilidad) y latest (por conveniencia).
  • docker/build-push-action@v6: Construye el Dockerfile y pushea la imagen. Las lineas cache-from y cache-to habilitan el cache de GitHub Actions para las capas de Docker, que explicamos a continuacion.

Caching: haciendo CI rapido

Los pipelines de CI que tardan 10 minutos se convierten rapidamente en un cuello de botella. Los desarrolladores dejan de esperar los resultados, empiezan a mergear sin verificar y se pierde todo el sentido de CI. El caching es como mantenes las cosas rapidas.


Hay dos cosas que vale la pena cachear en un proyecto Node.js: paquetes npm y capas de Docker.


npm cache es lo mas facil. La action actions/setup-node@v4 lo maneja por vos cuando agregas cache: "npm":


      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: "22"
          cache: "npm"

Esto cachea el cache de descarga de npm (no node_modules), asi que npm ci todavia corre pero no necesita descargar paquetes del registry. La primera ejecucion llena el cache y las siguientes lo reutilizan. En un proyecto con muchas dependencias, esto puede ahorrar 30 a 60 segundos por ejecucion.


Docker layer cache tiene mas impacto. Construir una imagen Docker desde cero cada vez es un desperdicio porque la mayoria de las capas (como la imagen base y los paquetes del sistema instalados) rara vez cambian. Docker Buildx con el backend de cache de GitHub Actions guarda las capas entre ejecuciones:


      - name: Build and push
        uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

  • cache-from: type=gha: Trae capas cacheadas del cache de GitHub Actions.
  • cache-to: type=gha,mode=max: Pushea todas las capas al cache despues de construir. La opcion mode=max cachea las capas intermedias tambien, no solo las capas de la imagen final.

Un Dockerfile bien estructurado se beneficia enormemente de esto. Si tu capa de instalacion de dependencias no cambio, Docker reutiliza la capa cacheada en vez de correr npm ci de nuevo dentro del contenedor. Esto puede reducir los tiempos de build de minutos a segundos.


Matrix builds: testeando entre versiones

A veces necesitas testear tu codigo contra multiples versiones de Node.js, o multiples sistemas operativos, o ambos. Los matrix builds te permiten definir un conjunto de variables y correr el job una vez por cada combinacion.


  test:
    name: Test (Node ${{ matrix.node-version }})
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: ["20", "22"]
      fail-fast: false

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
          cache: "npm"

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm test

Esto corre el job de test dos veces: una con Node 20 y otra con Node 22. Ambas ejecuciones pasan en paralelo en runners separados, asi que no ralentiza tu pipeline.


Configuraciones clave:


  • strategy.matrix: Define las variables y sus valores. Podes agregar mas dimensiones, como os: [ubuntu-latest, windows-latest], y GitHub va a correr cada combinacion.
  • fail-fast: false: Por defecto, si un job de la matrix falla, GitHub cancela los demas. Poner esto en false deja que todos los jobs terminen, asi podes ver todas las fallas a la vez.

Los matrix builds son especialmente utiles para librerias que necesitan soportar multiples runtimes. Para codigo de aplicacion donde vos controlas el runtime, testear una sola version suele ser suficiente.


Secrets y variables de entorno

Tu pipeline de CI va a necesitar credenciales frecuentemente: API keys para servicios externos, tokens para registries o passwords de bases de datos para tests de integracion. GitHub provee dos mecanismos para esto.


Variables de entorno son para valores no sensibles:


    env:
      NODE_ENV: test
      API_URL: https://api.staging.example.com

    steps:
      - name: Run tests
        run: npm test
        env:
          DATABASE_URL: postgres://localhost:5432/testdb

Podes definir variables de entorno a nivel de workflow, de job o de step. Las variables a nivel de step sobreescriben las de nivel de job, que sobreescriben las de nivel de workflow.


Secrets son para valores sensibles como API keys y tokens:


      - name: Deploy to staging
        run: ./deploy.sh
        env:
          DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

Para agregar secrets, anda a Settings de tu repositorio, despues Secrets and variables, despues Actions. Los secrets se encriptan en reposo y se enmascaran en los logs. GitHub va a reemplazar el valor del secret con *** si accidentalmente aparece en la salida.


Reglas importantes sobre secrets:


  • Nunca hardcodees secrets en tus archivos de workflow. Estan commiteados al repositorio y son visibles para cualquiera con acceso de lectura.
  • GITHUB_TOKEN es automatico. No necesitas crearlo. GitHub genera uno para cada ejecucion de workflow con permisos limitados al repositorio.
  • Los secrets no estan disponibles en pull requests de forks. Esta es una funcionalidad de seguridad. Si tus tests necesitan secrets, van a fallar en PRs de forks, lo cual es esperado.
  • Usa environments para secrets de deployment. Los environments de GitHub te permiten requerir aprobaciones y restringir que ramas pueden usar ciertos secrets.

Workflows reutilizables: manteniendo las cosas DRY

A medida que tu organizacion crece, vas a tener multiples repositorios que necesitan pipelines de CI similares. Copiar y pegar archivos YAML entre repositorios es una pesadilla de mantenimiento. Los workflows reutilizables te permiten definir un workflow una vez y llamarlo desde otros workflows.


Primero, crea el workflow reutilizable en un repositorio compartido. La diferencia clave es el trigger workflow_call:


# .github/workflows/node-ci.yml (en tu repo compartido)
name: Node.js CI

on:
  workflow_call:
    inputs:
      node-version:
        description: "Version de Node.js a usar"
        required: false
        type: string
        default: "22"
      run-lint:
        description: "Si correr linting o no"
        required: false
        type: boolean
        default: true

jobs:
  lint:
    name: Lint
    runs-on: ubuntu-latest
    if: ${{ inputs.run-lint }}

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: ${{ inputs.node-version }}
          cache: "npm"

      - run: npm ci

      - run: npx eslint . --max-warnings 0

  test:
    name: Test
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: ${{ inputs.node-version }}
          cache: "npm"

      - run: npm ci

      - run: npm test

Despues lo llamas desde cualquier repositorio:


# .github/workflows/ci.yml (en tu repo del proyecto)
name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  ci:
    uses: your-org/shared-workflows/.github/workflows/node-ci.yml@main
    with:
      node-version: "22"
      run-lint: true

Los beneficios son significativos:


  • Unica fuente de verdad: Actualiza el workflow compartido y cada repositorio que lo usa recibe la actualizacion.
  • Consistencia: Cada proyecto sigue el mismo proceso de CI, mismas versiones de actions, misma estrategia de caching.
  • Menos mantenimiento: Arregla un bug o actualiza una action en un lugar, no en cincuenta repositorios.
  • Los inputs lo hacen flexible: Cada proyecto puede personalizar el comportamiento (version de Node, si hacer lint o no, etc.) sin forkear el workflow.

Status badges: mostra la salud de tu pipeline

Una vez que tu pipeline de CI esta funcionando, queres que todos vean su estado de un vistazo. GitHub provee badges de estado que podes agregar a tu README:


![CI](https://github.com/your-org/your-repo/actions/workflows/ci.yml/badge.svg)

Esto se renderiza como un badge chiquito que muestra “passing” (verde) o “failing” (rojo) basado en la ultima ejecucion del workflow. Agregalo al principio de tu README para que los colaboradores sepan inmediatamente la salud del proyecto.


Tambien podes hacer badges especificos por rama:


![CI](https://github.com/your-org/your-repo/actions/workflows/ci.yml/badge.svg?branch=main)

Esto solo refleja el estado del workflow en la rama main, ignorando las feature branches.


Branch protection: requerir que CI pase antes de mergear

Un pipeline de CI solo es util si la gente no puede saltearselo. Las reglas de branch protection aseguran que el codigo no se pueda mergear a main a menos que CI pase. Aca te explico como configurarlo:


  1. Anda a Settings de tu repositorio, despues Branches.
  2. Clickea “Add branch protection rule” (o “Add classic branch protection rule”).
  3. Pone el patron de nombre de rama en main.
  4. Marca “Require status checks to pass before merging.”
  5. Busca y selecciona los nombres de tus jobs de CI (por ejemplo, “Lint”, “Test”).
  6. Opcionalmente marca “Require branches to be up to date before merging” para evitar mergear ramas desactualizadas.

Con esto configurado, el boton de merge en un pull request esta deshabilitado hasta que todas las verificaciones requeridas pasen. Nadie puede saltear CI, ni siquiera los admins del repositorio (a menos que lo sobreescriban explicitamente, lo cual deja un registro de auditoria).


Protecciones adicionales que vale la pena habilitar:


  • Requerir reviews de pull request: Al menos un miembro del equipo debe aprobar antes de mergear.
  • Requerir historia lineal: Forzar squash o rebase merges para una historia de git limpia.
  • No permitir saltear las configuraciones anteriores: Incluso los admins deben seguir las reglas.

El archivo de workflow completo

Aca esta el pipeline de CI completo combinando todo lo que cubrimos. Este es un punto de partida listo para produccion para cualquier proyecto TypeScript:


# .github/workflows/ci.yml
name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

env:
  NODE_ENV: test
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  lint:
    name: Lint
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: "22"
          cache: "npm"

      - name: Install dependencies
        run: npm ci

      - name: Check formatting
        run: npx prettier --check .

      - name: Run ESLint
        run: npx eslint . --max-warnings 0

      - name: Type check
        run: npx tsc --noEmit

  test:
    name: Test (Node ${{ matrix.node-version }})
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: ["20", "22"]
      fail-fast: false

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
          cache: "npm"

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm test

      - name: Run tests with coverage
        run: npm run test:coverage

      - name: Upload coverage report
        if: matrix.node-version == '22'
        uses: actions/upload-artifact@v4
        with:
          name: coverage-report
          path: coverage/
          retention-days: 14

  build:
    name: Build and Push Docker Image
    runs-on: ubuntu-latest
    needs: [lint, test]
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'

    permissions:
      contents: read
      packages: write

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Log in to GHCR
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=sha,prefix=
            type=raw,value=latest

      - name: Build and push
        uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

Fijate en algunas cosas de este workflow completo:


  • Tres etapas: Lint, test y build. Forman un pipeline donde cada etapa filtra la siguiente.
  • Type checking en lint: Agregamos tsc --noEmit para atrapar errores de TypeScript. Este es un chequeo barato que atrapa toda una clase de bugs.
  • Prettier check: prettier --check verifica el formato sin modificar archivos. Si un desarrollador se olvido de formatear, CI lo atrapa.
  • Coverage solo se sube una vez: Cuando corres un matrix build, solo necesitas un reporte de coverage, no uno por version de Node. El condicional if: matrix.node-version == '22' maneja esto.
  • Dias de retencion: Los artefactos no necesitan existir para siempre. Poner retention-days: 14 mantiene las cosas ordenadas.
  • Variables de entorno arriba: REGISTRY e IMAGE_NAME se definen una vez y se reutilizan, haciendo que el workflow sea mas facil de adaptar a otros registries.

Debuggeando workflows fallidos

Cuando tu pipeline de CI falle (y va a fallar), aca tenes como debuggearlo:


  • Lee los logs: Clickea en el job fallido en la UI de GitHub Actions. Cada step muestra su salida. El error generalmente esta en las ultimas lineas del step fallido.
  • Corre localmente primero: Antes de pushear, corre los mismos comandos localmente. npm ci && npx eslint . && npm test deberia producir el mismo resultado que CI.
  • Verifica el entorno del runner: CI corre en una maquina Ubuntu limpia. Si algo funciona localmente pero falla en CI, la diferencia suele estar en variables de entorno, herramientas instaladas o rutas de archivos.
  • Usa act para testing local: La herramienta act (https://github.com/nektos/act) te permite correr workflows de GitHub Actions en tu maquina local usando Docker. No es perfecto, pero atrapa la mayoria de los problemas.
  • Habilita el logging de debug: Re-ejecuta el workflow con logging de debug habilitado yendo a la ejecucion fallida, clickeando “Re-run all jobs” y marcando “Enable debug logging.” Esto agrega salida verbosa de cada action.

Errores comunes y como evitarlos

Algunas cosas que complican a la gente cuando configura CI por primera vez:


  • No usar npm ci: Usar npm install en CI puede producir arboles de dependencias diferentes a tu maquina local. Siempre usa npm ci, que instala exactamente lo que esta en package-lock.json.
  • Falta package-lock.json en el repositorio: Si lo pusiste en el gitignore, npm ci va a fallar. El lockfile siempre deberia estar commiteado.
  • Tests que dependen del orden: Si tus tests pasan localmente pero fallan en CI, podrian depender del orden de ejecucion. Vitest corre tests en paralelo por defecto, lo que puede exponer esto.
  • Paths hardcodeados: Tests que referencian /Users/tunombre/proyecto/ van a fallar en un runner Linux. Usa paths relativos o variables de entorno.
  • Olvidarse del contexto de Docker: Si tu Dockerfile copia archivos con COPY . ., asegurate de que tu .dockerignore excluya node_modules, .git y otros directorios grandes.
  • Triggers demasiado amplios: Correr CI en cada push a cada rama desperdicia minutos de runner. Limita los triggers a main y pull requests apuntando a main.

Que viene despues

Ahora tenemos un pipeline de CI que hace lint, testea y construye nuestro codigo automaticamente. Pero CI es solo la mitad de la historia. Meter codigo en un contenedor es util, pero ese contenedor necesita ir a algun lado.


En el proximo articulo, vamos a abordar Continuous Deployment (CD): tomar la imagen Docker que acabamos de construir y deployarla a un entorno real. Vamos a cubrir estrategias de deployment, rollbacks y como hacer que los deployments sean aburridos (que es exactamente lo que queres que sean).


Espero que te haya resultado util y que lo hayas disfrutado, hasta la proxima!


Errata

Si encontras algun error o tenes alguna sugerencia, mandame un mensaje para que se corrija.

Tambien podes revisar el codigo fuente y los cambios en los fuentes aca



$ Comentarios

Online: 0

Por favor inicie sesión para poder escribir comentarios.

2026-05-03 | Gabriel Garrido