Skip to main content

Reusable workflows


Table of contents

  1. The reusability concept
  2. Creating a reusable workflow
  3. Calling a workflow
  4. Inputs and secrets
  5. Outputs
  6. Practical exercises


1 - The reusability concept

The problem

Types of reuse

TypeDescriptionUsage
Reusable workflowA complete workflowStandard process
Composite actionA group of stepsCommon tasks
Starter workflowA templateNew 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

TypeExample
string'production'
booleantrue
number30

🔝 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_call to make a workflow reusable
  • uses: to call a workflow (same repo or external)
  • inputs: parameters with types (string, boolean, number)
  • secrets: sensitive data passed explicitly
  • secrets: inherit to pass all secrets
  • outputs: return values to the calling workflow
  • Organization: centralize shared workflows

🔝 Back to table of contents


← Previous chapter | Next chapter: CI/CD pipelines →