Home/Learn/CI/CD & GitHub Actions/Reusable Workflows & Composite Actions

Reusable Workflows & Composite Actions

Intermediate
GitHub Actions

Reusable workflows let you define a pipeline once and call it from multiple repos. Composite actions bundle multiple steps into a single reusable unit — eliminating duplication across workflows.

Overview

As your GitHub Actions usage grows, duplication is the biggest problem: every repo has a slightly different Docker build job, every team re-implements the same test setup. GitHub provides two reuse mechanisms: Reusable Workflows (call-workflow) for entire jobs or job sequences — used when you want to standardise the full pipeline across repos. Composite Actions for sharing a sequence of steps within a single job — used when you have a common setup sequence (configure AWS, set up Node, restore cache) that appears in many places. Organisation-level standardisation with reusable workflows is how platform engineering teams enforce consistent CI/CD practices.

Reusable Workflows

A workflow becomes reusable by adding workflow_call to its triggers. Caller workflows invoke it with uses: instead of normal job steps.

Reusable workflow definition and caller
# ── REUSABLE WORKFLOW: .github/workflows/build-and-push.yml ──────────────────

# (can be in the same repo or a shared .github repo)

name: Build and Push Docker Image (Reusable)



on:

  workflow_call:                    # makes this workflow callable

    inputs:

      image-name:

        required: true

        type: string

      environment:

        required: false

        type: string

        default: staging

    secrets:

      registry-token:

        required: true

    outputs:

      image-digest:

        description: "SHA256 digest of the pushed image"

        value: ${{ jobs.build.outputs.digest }}



jobs:

  build:

    runs-on: ubuntu-latest

    outputs:

      digest: ${{ steps.push.outputs.digest }}

    steps:

      - uses: actions/checkout@v4

      - uses: docker/setup-buildx-action@v3

      - uses: docker/login-action@v3

        with:

          registry: ghcr.io

          password: ${{ secrets.registry-token }}

      - name: Build and push

        id: push

        uses: docker/build-push-action@v5

        with:

          push: true

          tags: ghcr.io/${{ inputs.image-name }}:latest

          cache-from: type=gha

          cache-to: type=gha,mode=max



# ── CALLER WORKFLOW: in any repo ─────────────────────────────────────────────

name: Release



on:

  push:

    branches: [main]



jobs:

  build:

    uses: my-org/.github/.github/workflows/build-and-push.yml@main

    with:

      image-name: my-org/my-app

      environment: production

    secrets:

      registry-token: ${{ secrets.GITHUB_TOKEN }}



  deploy:

    needs: build

    runs-on: ubuntu-latest

    steps:

      - run: echo "Deployed image digest: ${{ needs.build.outputs.image-digest }}"

Composite Actions

Composite actions bundle multiple steps into a single action.yml file. They live in a repo under .github/actions/ and are referenced with uses: ./path/to/action or uses: org/repo/path@ref.

Composite action definition and usage
# .github/actions/setup-node-project/action.yml

name: Setup Node Project

description: Checkout, setup Node, restore cache, install deps



inputs:

  node-version:

    description: Node.js version

    default: '20'

  working-directory:

    description: Project directory

    default: '.'



outputs:

  cache-hit:

    description: Whether node_modules cache was hit

    value: ${{ steps.cache.outputs.cache-hit }}



runs:

  using: composite               # composite = multiple steps

  steps:

    - uses: actions/checkout@v4



    - uses: actions/setup-node@v4

      with:

        node-version: ${{ inputs.node-version }}



    - name: Restore cache

      id: cache

      uses: actions/cache@v4

      with:

        path: ${{ inputs.working-directory }}/node_modules

        key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}



    - name: Install dependencies

      if: steps.cache.outputs.cache-hit != 'true'

      shell: bash

      working-directory: ${{ inputs.working-directory }}

      run: npm ci



# Use the composite action in any workflow:

jobs:

  test:

    runs-on: ubuntu-latest

    steps:

      - uses: ./.github/actions/setup-node-project    # local reference

        with:

          node-version: '20'

      - run: npm test



  lint:

    runs-on: ubuntu-latest

    steps:

      - uses: ./.github/actions/setup-node-project

      - run: npm run lint

Self-Hosted Runners

GitHub-hosted runners are ephemeral VMs. Self-hosted runners run on your own infrastructure — necessary for private network access, GPU workloads, or specific hardware. Scale them with Actions Runner Controller (ARC) on Kubernetes.

Self-hosted runners and Actions Runner Controller
# When to use self-hosted runners:

# - Need access to private network (internal databases, private registries)

# - Need specific hardware (GPU for ML, large RAM, fast NVMe)

# - Cost at scale (GitHub-hosted is ~$0.008/minute; self-hosted is free)

# - Compliance: code must not leave your infrastructure



# Register a self-hosted runner (manual, for one machine):

# Settings → Actions → Runners → New self-hosted runner

# Follow the curl+configure+run instructions on your machine



# Actions Runner Controller (ARC) — Kubernetes-based auto-scaling runners

helm install arc \

  --namespace arc-systems \

  --create-namespace \

  oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set-controller



helm install arc-runner-set \

  --namespace arc-runners \

  --create-namespace \

  --set githubConfigUrl=https://github.com/my-org \

  --set githubConfigSecret=arc-controller-manager \

  oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set



# Use self-hosted runner in workflow:

jobs:

  build:

    runs-on: self-hosted             # any available self-hosted runner

    # or with labels:

    runs-on: [self-hosted, linux, gpu, high-memory]



# ARC scales runners from 0 to N based on queue depth

# Each runner pod is ephemeral — starts fresh per job (no persistent state)

Key Points to Remember

  • 1Reusable workflows (workflow_call) share entire job sequences — best for org-wide pipeline standards.
  • 2Composite actions share step sequences within a job — best for common setup patterns.
  • 3Reusable workflows support inputs, secrets, and outputs — fully parameterisable.
  • 4Self-hosted runners are needed for private network access, special hardware, or compliance.
  • 5Actions Runner Controller (ARC) auto-scales self-hosted runners on Kubernetes from 0 to N.
  • 6Publish composite actions to a dedicated .github repo to share across all org repositories.

Interview Questions

Sign in to ask Aria
1

What is the difference between a reusable workflow and a composite action?

Ask Aria about Reusable Workflows & Composite Actions

Your personal AI tutor — ask anything about this concept

Revision Status

Personal Notes

Sign in to save personal notes for this topic.

Discussion

Sign in to join the discussion.

Loading discussion…