GitHub Actions desde cero: workflows, jobs, steps y runners

DUGLAS MORENODUGLAS MORENO 👁 3

Introducción

GitHub Actions es la plataforma de integración continua (CI) y entrega continua (CD) que viene integrada con GitHub. Permite automatizar el flujo de trabajo completo de desarrollo de software: desde la ejecución de pruebas y la construcción del código hasta la implementación en servidores o la publicación en el ecosistema de GitHub. Aunque al principio puede parecer un poco intimidante, una vez que entendés los cuatro pilares básicos —workflows, jobs, steps y runners—, podés crear tuberías de CI/CD flexibles y potentes a medida que avanzas.

Este artículo está pensado para desarrolladores, ingenieros de confiabilidad, equipos DevOps y cualquier persona que quiera llevar su flujo de trabajo más allá del sistema de CI local. Al final, tendrás un workflow mínimo en YAML, una explicación clara de cada componente y un diagrama visual que conecta todo. Además, verás algunas prácticas recomendadas y consejos para evitar errores comunes.

Conceptos básicos

Qué es un workflow

En GitHub Actions, un workflow es el archivo YAML principal que define toda la tubería. Se guarda en el repositorio bajo .github/workflows/. Un workflow puede tener uno o más jobs, cada uno de los cuales representa una tarea independiente (por ejemplo, ejecutar tests, construir una imagen Docker, implementar un sitio web).

Un workflow puede activarse por diferentes eventos:

  • push – cuando se sube nuevo código a un branch.
  • pull_request – cuando se crea o actualiza una solicitud de incorporación de cambios.
  • schedule – ejecución periódica (por ejemplo, cada hora o diaria).
  • workflow_dispatch – permite una ejecución manual a pedido.

El archivo del workflow debe tener una extensión .yml o .yaml, un nombre único y una sección on que describa el desencadenador.

Jobs: tareas paralelas

Un job representa una unidad de trabajo que se ejecuta en un runner. Los jobs pueden ser:

  • Secuenciales – los jobs posteriores esperan a que los anteriores terminen.
  • Paralelos – múltiples jobs se ejecutan a la vez (útil para pruebas en paralelo).
  • Matriz – un solo job que se ejecuta múltiples veces con diferentes entradas (framework, versión de Node, parámetros de prueba).

Cada job tiene:

  • runs-on: el tipo de runner a usar (por ejemplo, ubuntu-latest).
  • strategy: permite matrix y ejecución en paralelo.
  • steps: un ordenado listado de acciones y comandos para ejecutar.

Steps: el trabajo detallado

Un step es la parte más pequeña de ejecución. Puede ser:

  • Ejecutar un comando directamente con run: (shell, PowerShell, etc.).
  • Usar una acción (uses:) – una pieza reutilizable de código, por ejemplo, actions/checkout@v4 para traer el código al runner.
  • Composición o reutilización con uses desde una acción compuesta.

Los steps se ejecutan en el orden en que aparecen, y cada paso puede depender del éxito/fallo de pasos anteriores.

Runners: donde se ejecuta el trabajo

Un runner es un servidor (proveído por GitHub o auto-alojado) que ejecuta los steps de un job. GitHub ofrece:

  • ubuntu-latest (Linux)
  • windows-latest (Windows)
  • macOS-latest (macOS)

También puedes configurar tu propio runner (auto-alojado) si necesitas un entorno con herramientas especializadas, acceso a red local o hardware específico.

Construyendo tu primer workflow

Paso a paso

A continuación tenés un archivo .github/workflows/hello-world.yml mínimo. No hay necesidad de instalar nada extra; solo necesitás un repositorio en GitHub.

name: Hello World Workflow

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

jobs:
  hello:
    runs-on: ubuntu-latest
    steps:
      - name: Mostrar el evento de GitHub
        run: |
          echo "Evento: ${{ github.event_name }}"
          echo "Rama: ${{ github.ref }}"

      - name: Verificar el código
        uses: actions/checkout@v4

      - name: Mostrar archivos
        run: find . -type f -maxdepth 2

Explicación

  • name: – título legible para humanos del workflow.
  • on: – tres desencadenadores: push a main, pull_request a main y ejecución manual.
  • jobs.hello: – un solo job llamado hello.
  • runs-on: – selecciona un runner Linux de GitHub.
  • steps: – tres pasos:
    1. Mostrar el evento de GitHub – imprime el tipo de evento y la rama.
    2. Verificar el código – usa la acción oficial actions/checkout para bajar el código fuente al runner.
    3. Mostrar archivos – recorre archivos en el árbol (útil para depuración).

Puedes subir este archivo a un repositorio, hacer un commit y ver el workflow en acción en la pestaña Actions. Cada ejecución creará un run, que contiene un job y pasos que se ejecutan en un runner.

Ejecutar el workflow manualmente

Después de hacer push del archivo, podés probar el workflow sin modificar el código. Haz clic en Actions → busca el workflow → haz clic en Run workflow (si está disponible gracias al desencadenador workflow_dispatch). La interfaz mostrará logs en vivo:

Ejecutando "Hello World Workflow" #123
Ejecutando job "hello"
Paso 1: Mostrar el evento de GitHub
Evento: workflow_dispatch
Rama: refs/heads/main
Paso 2: Verificar el código
Descargando el código... (100%)
Paso 3: Mostrar archivos
.
.

Un ejemplo de script ejecutable

Para demostrar un paso que realmente imprime algo, podés incluir un pequeño script Bash:

echo "¡Hola desde el runner!"
echo "La versión de bash es:"
bash --version

El bloque anterior es un ejemplo ejecutable: podés copiarlo y pegarlo en un repositorio, ejecutar el workflow y ver la salida directamente en la página de GitHub Actions.

Profundizando en jobs y steps

Strategy y ejecución en paralelo

Supongamos que querés probar una aplicación Node.js contra múltiples versiones de Node. Usás un matrix dentro de strategy:

strategy:
  matrix:
    node-version: [16, 18, 20]

Y luego referenciar matrix.node-version en cada job:

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [16, 18, 20]
    steps:
      - name: Configurar Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
      - name: Instalar dependencias
        run: npm ci
      - name: Ejecutar tests
        run: npm test

Ahora el job test se ejecuta tres veces en paralelo (a menos que agregues max-parallel). Cada iteración obtiene su propio runner.

Paso compuesto vs. reusable

GitHub Actions soporta acciones compuestas (YAML dentro de uses) y workflows reutilizables (workflow_call). Las primeras son útiles dentro de un workflow; las segundas permiten compartirlibrerías entre repositorios. Ambas ayudan a evitar la duplicación de código.

Salida y condiciones

Los pasos pueden exponer valores usando id + set-output (legacy) o GITHUB_OUTPUT. Ejemplo:

- name: Calcular versión
  id: version
  run: |
    VERSION=$(date +%Y.%m.%d)
    echo "version=$VERSION" >> $GITHUB_OUTPUT

Luego usás ${{ steps.version.outputs.version }} en cualquier paso posterior o en toda la matriz.

Las condiciones (if:) permiten saltar un paso basándose en variables de entorno, resultados anteriores o cadenas de edición. Ejemplo para solo ejecutar un paso si el branch es main:

- name: Despliegue solo en main
  if: github.ref == 'refs/heads/main'
  run: ./deploy.sh

Manejo de errores y reintentos

Añadí continue-on-error: true si querés que el job continúe a pesar de que un paso falle, o retries: dentro de strategy para reintentos automáticos en pasos fallidos.

Runners: eligiendo el runner adecuado

GitHub-hosted

Los runners alojados son administrados por GitHub. Ofrecen un entorno limpio y actualizado para cada job. La tabla a continuación resume las imágenes disponibles:

OS Imagen Uso típico
Linux ubuntu-latest Construcción multiplataforma, pruebas de contenedores
Windows windows-latest Construcción .NET, scripts PowerShell
macOS macos-latest Aplicaciones iOS, scripts Ruby/Bundler

También podés seleccionar una versión específica (ubuntu-22.04) si necesitás un entorno exacto.

Auto-hosted (on-premise)

Un runner auto-alojado es un servidor que controlas. Ventajas:

  • Instalación de dependencias especializadas (JDKs, bases de datos, drivers).
  • Acceso a recursos de red o hardware no disponibles en runners GitHub (por ejemplo, dispositivos IoT, GPU).
  • Control de costos si ejecutas jobs pesados en tu propio infraestructura.

Configurarlo implica:

  1. Crear un token de registro en GitHub (settings > Actions > Runners).
  2. Descargar el script de configuración (./config.sh).
  3. Ejecutarlo con runner-group-id, work-folder y el token.

Una vez registrado, podés asignar el runner a uno o más grupos para restringir qué repositorios pueden usarlo.

Runners personalizados

Si los runners estándar no cubren tus necesidades, podés crear un runner personalizado con cualquier sistema operativo (por ejemplo, Alpine Linux, containers base). Esto te permite empaquetar dependencias en una imagen Docker y usar container: en un job:

jobs:
  build:
    runs-on: ubuntu-latest
    container:
      image: myregistry/custom-builder:1.2
    steps:
      - uses: actions/checkout@v4
      - run: npm run build

El contenedor actúa como el shell del runner, dándote un entorno aislado y reproducible.

Diagrama: relacionando todo

A continuación tenés un diagrama Mermaid que visualiza la relación entre cada concepto. Funciona en GitHub y en cualquier visor compatible con Markdown.

graph TD
    A[Workflow] --> B[Job 1]
    A --> C[Job 2]
    B --> D[Step 1]
    B --> E[Step 2]
    C --> F[Step 1]
    D --> G[Runner]
    E --> G
    F --> G
    G --> H[Ejecutar comandos o acciones]

El diagrama muestra:

  • Un workflow que activa dos jobs.
  • Cada job contiene uno o más steps.
  • Todos los steps se ejecutan en un runner (alojado o auto-alojado).

Visualizar esto ayuda a entender que un workflow es la estructura, un job es una tarea, un step es el trabajo detallado y el runner es el entorno de ejecución.

Mejores prácticas

1. Mantener el YAML limpio

  • Usa espacios consistentes (2 espacios).
  • Agrupa las secciones lógicamente: name, on, jobs.
  • Agrega comentarios para jobs o steps de mayor nivel.

2. Caché de dependencias

Si tu workflow instala npm, pip o bundles de gems, agrega un paso actions/cache@v4 para evitar reconstruir desde cero:

- name: Caché de npm
  uses: actions/cache@v4
  with:
    path: ~/.npm
    key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
    restore-keys: |
      ${{ runner.os }}-node-

3. Uso seguro de secrets

Los secrets (claves API, tokens) están cifrados y disponibles solo en el runner. Distribuye el menor número posible de secrets; prefiere los repository secrets sobre environment secrets a menos que necesites separación por entorno.

4. Least privilege y GitHub Tokens

Los workflows usan un token de GitHub para realizar acciones en nombre del usuario que disparó el workflow. Considera restringir los permisos (por ejemplo, solo contents:read si no necesitas escribir). Puedes editar permissions: dentro del job:

jobs:
  build:
    permissions:
      contents: read
      packages: write
    ...

5. Manejo de errores y reintentos

  • continue-on-error: true para pasos que fallan ocasionalmente (por ejemplo, tests de red).
  • retries: 3 dentro de un step (ante actions/setup-node o run) si fallan por razones transitorias.

6. Etiquetar runs para mejor depuración

Agrega if: always() a pasos de registro para que siempre aparezcan, incluso si pasos anteriores fallan.

Errores comunes y cómo evitarlos

Síntoma Causa probable Solución
Los pasos no se ejecutan en el orden deseado Asumir que la paralelización es implícita Usa IDs explícitos (needs: y if) para controlar la ejecución.
El workflow falla con Error: Invalid environment Intentar usar container: en un runner Windows Asegúrate de que la imagen del contenedor sea compatible con el SO del runner.
Las ejecuciones se detienen después de un timeout No hay límite de tiempo en steps largos Agrega timeout-minutes: en cada job o step.
La caché nunca se restablece Clave incorrecta que no cambia entre commits Actualiza la clave con hashFiles('**/*.lock').
Token expira o tiene permisos insuficientes Reutilizar un token personal en lugar de un token generado por GitHub Usa GITHUB_TOKEN y limita los permisos.

Conclusión

GitHub Actions se construye sobre cuatro conceptos clave:

  1. Workflows – el archivo YAML principal y el disparador.
  2. Jobs – tareas independientes que pueden ser paralelas o en matriz.
  3. Steps – acciones atómicas (comando o acción reutilizable).
  4. Runners – servidores donde realmente se ejecutan los steps (alojados, auto-alojados o en contenedor).

Una vez que comprendés estas piezas y las relacionas con el diagrama, crear una tubería de CI/CD compleja es solo una cuestión de composición. Recuerda mantener el YAML legible, implementar estrategias de caché, proteger secrets y aplicar el principio de least privilege. Con estas bases, podés avanzar hacia despliegues avanzados, notificaciones y reutilización entre repositorios.

Próximos pasos

  • Explora GitHub Pages y environment protection rules.
  • Prueba workflows reutilizables (workflow_call) para compartir bibliotecas entre equipos.
  • Integra notificaciones usando actions/github-script para comentar en PRs o enviar Slack/Teams.

Con una comprensión sólida de workflows, jobs, steps y runners, estás listo para automatizar cualquier flujo de desarrollo. ¡Empieza a escribir tus primeros workflows hoy y observa cómo tu flujo de trabajo se vuelve más rápido e inteligente!

Comentarios (0)

Sé el primero en comentar.

Dejá tu comentario