GitHub Actions Reusable Workflows Tutorial
Stop copy-pasting CI YAML across repos. Learn how to build reusable GitHub Actions workflows with inputs, secrets, outputs, and per-environment overrides.
What you'll learn
- ✓The difference between composite actions and reusable workflows
- ✓How to define and call reusable workflows
- ✓Passing inputs, secrets, and outputs
- ✓Versioning and pinning patterns
- ✓A real multi-repo CI structure
Prerequisites
- •Basic experience writing GitHub Actions YAML
What and Why
Most teams accumulate near-identical CI YAML in every repo: same Node setup, same lint, same test, same Docker build. When you need to change one step (say, swap actions/checkout v3 for v4), you fan out PRs to 20 repos. Reusable workflows solve that. One workflow lives in a central repo, and other repos call it.
Mental Model
GitHub Actions has two reuse mechanisms:
- Composite actions: small reusable steps. They look like an
action.ymlwithruns.using: composite. Called like any other action viauses:. - Reusable workflows: whole workflows you call from another workflow with
uses:. They include aworkflow_calltrigger and acceptinputs,secrets, and produceoutputs.
Composite actions are for sequences of steps; reusable workflows are for entire jobs.
repo-a/.github/workflows/ci.yml
|
| jobs:
| build:
| uses: org/ci-workflows/.github/workflows/node-ci.yml@v1
| with: { node-version: '20' }
| secrets: inherit
v
org/ci-workflows/.github/workflows/node-ci.yml
on: workflow_call
jobs: lint, test, build Hands-on Example
A reusable workflow in org/ci-workflows:
# .github/workflows/node-ci.yml
name: Node CI
on:
workflow_call:
inputs:
node-version:
type: string
default: '20'
run-coverage:
type: boolean
default: false
secrets:
NPM_TOKEN:
required: false
outputs:
image-tag:
description: Built image tag
value: ${{ jobs.build.outputs.tag }}
jobs:
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 run lint
- run: npm test
- if: inputs.run-coverage
run: npm run coverage
build:
needs: test
runs-on: ubuntu-latest
outputs:
tag: ${{ steps.meta.outputs.tag }}
steps:
- uses: actions/checkout@v4
- id: meta
run: echo "tag=ghcr.io/${{ github.repository }}:${{ github.sha }}" >> $GITHUB_OUTPUT
A consumer in another repo:
# .github/workflows/ci.yml
name: CI
on:
pull_request:
push: { branches: [main] }
jobs:
ci:
uses: org/ci-workflows/.github/workflows/node-ci.yml@v1
with:
node-version: '20'
run-coverage: true
secrets: inherit
deploy:
needs: ci
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- run: echo "Deploying ${{ needs.ci.outputs.image-tag }}"
secrets: inherit forwards all org-level and repo-level secrets without listing them. Use it when you trust the callee; otherwise pass only what is needed.
Common Pitfalls
Pinning to a moving tag. @main means every workflow run uses whatever is currently on the callee’s main. Pin to a specific tag (@v1.2.0) or a SHA for production-critical workflows.
Forgetting permissions. Each job in a reusable workflow inherits the caller’s permissions: block by default. If the caller restricts permissions, callee actions may fail. Set explicit permissions in the reusable workflow:
permissions:
contents: read
packages: write
Secrets that do not exist. A required secret declared in workflow_call.secrets must be passed by the caller, or the workflow fails to start.
Cross-repo private callees. Calling a private reusable workflow from another private repo requires the callee repo to allow it under “Actions settings > Access”.
Nested matrix madness. A reusable workflow run as part of a matrix job multiplies cost quickly. Confirm runtime expectations before flipping the switch.
Practical Tips
Keep a single repo (often ci-workflows or actions-platform) for your reusable workflows and composite actions. Version it with semantic tags:
git tag v1.2.0
git push --tags
Maintain a sliding v1 tag that points to the latest non-breaking 1.x release. Most callers track v1; cautious ones pin to v1.2.0.
Combine reusable workflows with environments for staged deploys:
deploy:
uses: org/ci-workflows/.github/workflows/deploy.yml@v1
with: { env: production }
secrets: inherit
The callee references environment: ${{ inputs.env }} so deploy approvals, environment secrets, and protection rules apply.
Combine with OIDC for cloud auth. No long-lived AWS keys; the workflow exchanges a GitHub OIDC token for short-lived STS credentials.
Use composite actions for things like “setup repo”: checkout, language runtime, dependency cache. Compose multiple composite actions inside a reusable workflow for readable, layered YAML.
Add a workflow_dispatch trigger to your reusable workflow during development so you can run it standalone and test changes without modifying every caller.
Wrap-up
Reusable workflows turn duplicated CI YAML into a shared library you maintain in one place. Define them with workflow_call, accept inputs and secrets, expose outputs to chain into later jobs, and pin callers to immutable tags. Use composite actions for step-level reuse and reusable workflows for job-level reuse. Combine with OIDC and environments for safe deploys. Once the pattern is in place, updating CI for 50 repos becomes a single PR — which is exactly the leverage CI is supposed to give you.
Related articles
- CI/CD GitHub Actions vs Jenkins: CI/CD Compared
Compare GitHub Actions and Jenkins for CI/CD pipelines. Covers setup, configuration, plugins, pricing, and when to choose each tool for your team.
- CI/CD GitHub Actions: Complete CI/CD Guide for Any Project
Set up GitHub Actions CI/CD from scratch — workflows, jobs, caching, secrets, matrix builds, deployment, and reusable patterns for any language or framework.
- DevOps CI/CD Pipeline Design Fundamentals
How to design a CI/CD pipeline that stays fast, reliable, and reversible: stages, caching, parallelism, environments, and rollback strategies that scale with the team.
- CI/CD CI/CD Pipeline Best Practices for Faster Deployments
Build fast, reliable CI/CD pipelines — parallel stages, caching, fast feedback, trunk-based development, and deployment automation patterns.