Reusable workflows
Table of contents
- The reusability concept
- Creating a reusable workflow
- Calling a workflow
- Inputs and secrets
- Outputs
- Practical exercises
1 - The reusability concept
The problem
Types of reuse
| Type | Description | Usage |
|---|---|---|
| Reusable workflow | A complete workflow | Standard process |
| Composite action | A group of steps | Common tasks |
| Starter workflow | A template | New repo |
Advantages
- DRY: Don't repeat the code
- Maintenance: A single place to update
- Standards: Consistent processes
- Governance: Centralized control
🔝 Back to table of contents
2 - Creating a reusable workflow
Basic structure
# .github/workflows/reusable-build.yml
name: Reusable Build Workflow
on:
workflow_call: # Permet l'appel depuis d'autres workflows
inputs:
node-version:
description: 'Node.js version'
required: false
type: string
default: '18'
secrets:
npm-token:
description: 'NPM authentication token'
required: false
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
- run: npm ci
env:
NPM_TOKEN: ${{ secrets.npm-token }}
- run: npm run build
workflow_call
on:
workflow_call:
inputs:
# Input string
environment:
type: string
required: true
# Input booléen
deploy:
type: boolean
default: false
# Input nombre
timeout:
type: number
default: 30
secrets:
# Secret requis
api-key:
required: true
# Secret optionnel
slack-webhook:
required: false
outputs:
version:
description: 'Built version'
value: ${{ jobs.build.outputs.version }}
Input types
| Type | Example |
|---|---|
string | 'production' |
boolean | true |
number | 30 |
🔝 Back to table of contents
3 - Calling a workflow
From the same repo
# .github/workflows/ci.yml
name: CI
on: [push]
jobs:
call-build:
uses: ./.github/workflows/reusable-build.yml
with:
node-version: '20'
secrets:
npm-token: ${{ secrets.NPM_TOKEN }}
From another repo
name: CI
on: [push]
jobs:
call-build:
uses: owner/repo/.github/workflows/reusable-build.yml@main
with:
node-version: '20'
secrets:
npm-token: ${{ secrets.NPM_TOKEN }}
With inherit secrets
jobs:
call-workflow:
uses: ./.github/workflows/reusable.yml
secrets: inherit # Passe tous les secrets automatiquement
Chain workflows
name: Full Pipeline
on:
push:
branches: [main]
jobs:
build:
uses: ./.github/workflows/reusable-build.yml
with:
node-version: '18'
test:
needs: build
uses: ./.github/workflows/reusable-test.yml
deploy:
needs: test
uses: ./.github/workflows/reusable-deploy.yml
with:
environment: production
secrets: inherit
🔝 Back to table of contents
4 - Inputs and secrets
Input validation
on:
workflow_call:
inputs:
environment:
type: string
required: true
description: 'Must be staging or production'
jobs:
validate:
runs-on: ubuntu-latest
steps:
- name: Validate environment
run: |
if [[ "${{ inputs.environment }}" != "staging" && "${{ inputs.environment }}" != "production" ]]; then
echo "Invalid environment: ${{ inputs.environment }}"
exit 1
fi
Dynamic secrets
# Caller workflow
jobs:
deploy:
uses: ./.github/workflows/deploy.yml
with:
environment: ${{ github.ref == 'refs/heads/main' && 'production' || 'staging' }}
secrets:
deploy-key: ${{ github.ref == 'refs/heads/main' && secrets.PROD_KEY || secrets.STAGING_KEY }}
Organization secrets
# Le workflow réutilisable peut accéder aux secrets org
# si le repo appelant y a accès
jobs:
deploy:
uses: org/shared-workflows/.github/workflows/deploy.yml@main
secrets:
api-key: ${{ secrets.ORG_API_KEY }}
🔝 Back to table of contents
5 - Outputs
Define outputs
# Reusable workflow
on:
workflow_call:
outputs:
version:
description: 'The built version'
value: ${{ jobs.build.outputs.version }}
artifact-name:
description: 'Name of the uploaded artifact'
value: ${{ jobs.build.outputs.artifact }}
jobs:
build:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
artifact: ${{ steps.artifact.outputs.name }}
steps:
- id: version
run: echo "version=1.0.${{ github.run_number }}" >> $GITHUB_OUTPUT
- id: artifact
run: echo "name=build-${{ github.run_number }}" >> $GITHUB_OUTPUT
Use the outputs
# Caller workflow
jobs:
build:
uses: ./.github/workflows/reusable-build.yml
with:
node-version: '18'
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- run: |
echo "Deploying version: ${{ needs.build.outputs.version }}"
echo "Artifact: ${{ needs.build.outputs.artifact-name }}"
Complex outputs
# Reusable workflow
jobs:
build:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.generate.outputs.matrix }}
steps:
- id: generate
run: |
echo 'matrix={"include":[{"env":"staging"},{"env":"production"}]}' >> $GITHUB_OUTPUT
# Caller workflow
jobs:
build:
uses: ./.github/workflows/reusable-build.yml
deploy:
needs: build
strategy:
matrix: ${{ fromJSON(needs.build.outputs.matrix) }}
runs-on: ubuntu-latest
steps:
- run: echo "Deploying to ${{ matrix.env }}"
🔝 Back to table of contents
6 - Practical exercises
Exercise 1: Simple reusable workflow
Create a reusable Node.js build workflow:
Solution
# .github/workflows/reusable-node-build.yml
name: Reusable Node Build
on:
workflow_call:
inputs:
node-version:
type: string
default: '18'
jobs:
build:
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 build
# .github/workflows/ci.yml
name: CI
on: push
jobs:
build:
uses: ./.github/workflows/reusable-node-build.yml
with:
node-version: '20'
Exercise 2: Workflow with outputs
Create a workflow that returns the version:
Solution
# .github/workflows/reusable-version.yml
name: Version
on:
workflow_call:
outputs:
version:
value: ${{ jobs.generate.outputs.version }}
jobs:
generate:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.ver.outputs.version }}
steps:
- id: ver
run: echo "version=1.0.${{ github.run_number }}" >> $GITHUB_OUTPUT
# .github/workflows/release.yml
name: Release
on: push
jobs:
version:
uses: ./.github/workflows/reusable-version.yml
release:
needs: version
runs-on: ubuntu-latest
steps:
- run: echo "Releasing ${{ needs.version.outputs.version }}"
Quiz
Q1. Which trigger makes a workflow reusable?
Answer
workflow_call
Q2. How do you pass all secrets automatically?
Answer
secrets: inherit
🔝 Back to table of contents
Key takeaways
workflow_callto make a workflow reusableuses:to call a workflow (same repo or external)- inputs: parameters with types (string, boolean, number)
- secrets: sensitive data passed explicitly
secrets: inheritto pass all secrets- outputs: return values to the calling workflow
- Organization: centralize shared workflows