GitHub Actions Tutorial — Reusable Workflows Exercises

2 minute read

Practice Exercises — Reusable Workflows

Test your understanding of workflow_call, inputs, outputs, secrets, composition patterns, and org shared workflows with these hands-on exercises.


Exercise 1 — Basic Reusable Workflow

Task: Create:

  1. A reusable workflow build.yml that takes a node-version string input (default '20'), checks out code, installs dependencies, and runs npm run build
  2. A caller workflow ci.yml that triggers on push to main and calls build.yml with Node 20
Show Solution

build.yml:


name: Build

on:
  workflow_call:
    inputs:
      node-version:
        type: string
        default: '20'

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

ci.yml:


name: CI

on:
  push:
    branches: [main]

jobs:
  build:
    uses: ./.github/workflows/build.yml
    with:
      node-version: '20'


Exercise 2 — Outputs from Reusable Workflow

Task: Create a reusable workflow version.yml that:

  1. Generates a version string in format 1.0.<run_number>
  2. Returns it as an output named version

Then create a caller that prints the version.

Show Solution

version.yml:


name: Generate Version

on:
  workflow_call:
    outputs:
      version:
        description: 'Generated version'
        value: ${{ jobs.generate.outputs.version }}

jobs:
  generate:
    runs-on: ubuntu-latest
    outputs:
      version: ${{ steps.ver.outputs.version }}
    steps:
      - name: Generate version
        id: ver
        run: echo "version=1.0.${{ github.run_number }}" >> "$GITHUB_OUTPUT"

caller.yml:


name: Release

on:
  push:
    branches: [main]

jobs:
  version:
    uses: ./.github/workflows/version.yml

  release:
    needs: version
    runs-on: ubuntu-latest
    steps:
      - run: echo "Releasing version ${{ needs.version.outputs.version }}"

The output chain: step → job output → workflow output → caller needs.


Exercise 3 — Secrets Handling

Task: Create a reusable deployment workflow that:

  1. Accepts inputs: environment (string) and image-tag (string)
  2. Requires secrets: DEPLOY_TOKEN
  3. Runs a deployment step using the secret

Write two caller configurations: one passing the secret explicitly, one using secrets: inherit.

Show Solution

deploy.yml (reusable):


name: Deploy

on:
  workflow_call:
    inputs:
      environment:
        type: string
        required: true
      image-tag:
        type: string
        required: true
    secrets:
      DEPLOY_TOKEN:
        required: true

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: ${{ inputs.environment }}
    steps:
      - run: |
          echo "Deploying ${{ inputs.image-tag }} to ${{ inputs.environment }}"
          echo "Using token: ***"
        env:
          TOKEN: ${{ secrets.DEPLOY_TOKEN }}

Explicit secrets:


jobs:
  deploy:
    uses: ./.github/workflows/deploy.yml
    with:
      environment: staging
      image-tag: 'v2.1.0'
    secrets:
      DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}

Inherit all secrets:


jobs:
  deploy:
    uses: ./.github/workflows/deploy.yml
    with:
      environment: staging
      image-tag: 'v2.1.0'
    secrets: inherit


Exercise 4 — Linear Pipeline Composition

Task: Create a CI/CD pipeline with four reusable workflows called in sequence:

  1. lint.yml — runs linting
  2. test.yml — runs tests (takes node-version input)
  3. build.yml — builds the project
  4. deploy.yml — deploys (takes environment input)

Write the caller workflow that chains them: lint → test → build → deploy (to production).

Show Solution

name: CI/CD Pipeline

on:
  push:
    branches: [main]

jobs:
  lint:
    uses: ./.github/workflows/lint.yml

  test:
    needs: lint
    uses: ./.github/workflows/test.yml
    with:
      node-version: '20'

  build:
    needs: test
    uses: ./.github/workflows/build.yml
    secrets: inherit

  deploy:
    needs: build
    uses: ./.github/workflows/deploy.yml
    with:
      environment: production
    secrets: inherit

Each job waits for the previous one via needs:, creating a sequential pipeline.


Exercise 5 — Cross-Repo Shared Workflow

Task: Write a caller workflow that:

  1. Uses a reusable workflow from my-org/shared-workflows repository
  2. References the ci-node.yml workflow at tag v2
  3. Passes node-version: '20' as input
  4. Inherits secrets
Show Solution

name: CI

on:
  push:
    branches: [main]
  pull_request:

jobs:
  ci:
    uses: my-org/shared-workflows/.github/workflows/ci-node.yml@v2
    with:
      node-version: '20'
    secrets: inherit

Key points:

  • my-org/shared-workflows — the organization’s central workflow repository
  • @v2 — pinned to major version tag for stability
  • secrets: inherit — passes all caller secrets to the reusable workflow
  • The shared workflow repo must grant access (Settings → Actions → General)

← Back to: Chapter 10: Reusable Workflows

Next Chapter: Chapter 11: Custom Actions →

Updated: