🔄 GitHub Actions: Reusable Workflows
GitHub Actions Reusable Workflows allow you to avoid duplication of YAML code in projects with multiple repositories or microservices. With this functionality, you can define a pipeline template (for example, Docker build, Kubernetes deployment, or code quality analysis) in one place and call it from dozens of different repositories.
1. What is a Reusable Workflow and why use it?
In medium to large organizations, maintaining identical copies of pipelines in 50 different repositories leads to the maintenance nightmare known as DRY violation (Don't Repeat Yourself). If you need to update a version of Node.js or fix a security parameter in Helm, you would need to send 50 Pull Requests.
Key Concept: A Called Workflow exposes the on: workflow_call trigger. A Caller Workflow calls that pipeline within one of its jobs using the uses: directive.
2. Reusable Workflows vs. Custom Actions
It is common to confuse Reusable Workflows with Composite/Custom Actions. Although both promote code reuse, they resolve different levels of abstraction:
| Feature | Reusable Workflows | Composite / Custom Actions |
|---|---|---|
| Hierarchy Level | Complete Job Level. Can include multiple jobs in parallel/sequence. | Individual Step level within an existing job. |
| Runners / Environment | It manages its own runners (runs-on), permissions (permissions) and matrix. | It is executed within the runner already assigned by the job that invokes it. |
| Matrix and Parallelism | Supports complex arrays and multiple coordinated jobs. | It does not handle independent employment or direct execution matrices. |
| Secrets and Environments | Supports native integration with environment and prior approval. | Access the variables and secrets transmitted to the step. |
3. Reusable Workflow Syntax (Called Workflow)
The file is usually saved within .github/workflows/ (for example, _build_and_deploy.yml). The fundamental key lies in defining the activation event on: workflow_call.
name: Standard Build & Deploy Pipeline
on:
workflow_call:
# 1. Definición de Parámetros de Entrada
inputs:
environment:
description: 'Entorno de despliegue (staging, production)'
required: true
type: string
node_version:
description: 'Versión de Node.js'
required: false
default: '20'
type: string
run_tests:
description: 'Si se deben ejecutar pruebas unitarias'
required: false
default: true
type: boolean
# 2. Definición de Secretos Requeridos
secrets:
AWS_ROLE_ARN:
description: 'ARN del rol de AWS IAM mediante OIDC'
required: true
SLACK_WEBHOOK:
description: 'URL de webhook para notificaciones'
required: false
# 3. Salidas generadas para el caller
outputs:
image_tag:
description: 'Tag generado de la imagen Docker'
value: ${{ jobs.build.outputs.docker_tag }}
jobs:
build:
name: Build & Test
runs-on: ubuntu-latest
outputs:
docker_tag: ${{ steps.prep.outputs.tag }}
steps:
- name: Checkout del repositorio caller
uses: actions/checkout@v4
- name: Configurar Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node_version }}
- name: Instalar Dependencias
run: npm ci
- name: Ejecutar Tests
if: ${{ inputs.run_tests }}
run: npm test
- name: Generar Docker Tag
id: prep
run: echo "tag=${{ inputs.environment }}-${{ github.sha }}" >> $GITHUB_OUTPUT
deploy:
name: Deploy a K8s
needs: build
runs-on: ubuntu-latest
environment: ${{ inputs.environment }}
steps:
- name: Notificar inicio
run: echo "Desplegando imagen ${{ needs.build.outputs.docker_tag }} en ${{ inputs.environment }}"4. Invoking the Workflow (Caller Workflow)
The repository that wants to consume this standardized pipeline creates a normal workflow (triggered by push, pull_request, etc.) and calls the reusable workflow by defining uses: at the job level.
name: Production Release
on:
push:
branches:
- main
jobs:
# Llama al Reusable Workflow dentro del mismo repositorio (vía ruta relativa)
call-pipeline:
uses: ./.github/workflows/_build_and_deploy.yml
with:
environment: production
node_version: '20'
run_tests: true
secrets:
AWS_ROLE_ARN: ${{ secrets.PROD_AWS_ROLE_ARN }}
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
# Job posterior que utiliza las salidas (outputs) del reusable workflow
notify:
needs: call-pipeline
runs-on: ubuntu-latest
steps:
- name: Ver Tag Desplegado
run: |
echo "Despliegue finalizado exitosamente."
echo "Tag publicado: ${{ needs.call-pipeline.outputs.image_tag }}"5. Advanced Secret Management: secrets: inherit
Manually passing 10 or 15 secrets through the secrets: clause can be tedious. GitHub Actions provides the secrets: inherit directive for the reusable workflow to automatically inherit all repository and organization secrets.
call-central-pipeline:
# El llamado hereda implícitamente todos los secretos accesibles en el caller
uses: mi-organizacion/ci-templates/.github/workflows/deploy.yml@v2
with:
environment: staging
secrets: inherit6. Centralization between Multiple Repositories (Cross-Repository)
The biggest advantage of Reusable Workflows is unlocked by hosting your templates in a centralized repository within your organization (e.g. mi-org/devops-templates).
Reference by Git Reference (Branch, Tag or Commit SHA):
- By Semantic Tag (Recommended for production):
uses: mi-org/devops-templates/.github/workflows/docker-build.yml@v1.2.0
- By Branch (Development/Testing):
uses: mi-org/devops-templates/.github/workflows/docker-build.yml@main
- By Commit SHA (Maximum security and immutability):
uses: mi-org/devops-templates/.github/workflows/docker-build.yml@a1b2c3d4e5f6...
Setting Permissions in GitHub Org: So that other private repositories in your organization can invoke the reusable workflow hosted in a central private repo, you must go to:
Repository Settings → Actions → General → Access and select "Accessible from repositories in the 'NAME' organization".
7. Good Practices and Golden Rules
- Prefix with underscore: Name your internal reusable workflows starting with an underscore (ex:
_terraform-pipeline.yml) to visually differentiate them from standard input workflows.
- Nesting Limit: You can nest Reusable Workflows (one reusable workflow calling another), but there is a maximum limit of 4 levels deep.
- Versioning Strategy: Use semantic releases (tags
v1,v1.1.0) in your centralized pipeline repository to not break consuming projects when you make backwards incompatible changes.
- Avoid hidden dependencies: Explicitly declare all necessary
inputsandsecretsinon: workflow_callto have self-contained documentation.