Skip to content
CloudOps
Newsletter Sign up
All guides
AI for Automation By James Joyner IV · · 10 min read

GitHub Actions Reusable Workflows for Automation at Scale

Copy-pasting CI YAML across 40 repos is how drift starts. Reusable workflows and composite actions centralize your pipeline logic so one fix lands everywhere.

  • #automation
  • #github-actions
  • #ci-cd
  • #devops
  • #yaml

The day I decided to take reusable workflows seriously was the day I had to patch a security issue in our deploy pipeline and discovered the same forty lines of YAML copy-pasted into thirty-eight repositories — each one subtly different because they’d drifted over two years of independent edits. There was no single fix. There were thirty-eight fixes, and I had to eyeball every one to make sure the “fix” matched what that repo actually did.

That is the problem reusable workflows and composite actions solve. They let you write your pipeline logic once, version it, and call it from every repo — so a single change propagates everywhere. If you run CI across more than a handful of repos, this is the structural change that pays for itself fastest.

Two tools: reusable workflows vs composite actions

GitHub gives you two ways to share automation, and people conflate them. They’re for different jobs.

A composite action bundles a sequence of steps into one reusable step. You call it inside a job with uses:. Good for “set up our toolchain” or “authenticate to the cloud” — a chunk of steps you repeat inside jobs.

A reusable workflow is an entire workflow (jobs, runners, the lot) that another workflow calls with uses: at the job level. Good for “the whole build-test-deploy pipeline.” It can take inputs and secrets and run its own jobs.

Rule of thumb: composite action for a repeated group of steps, reusable workflow for a repeated whole pipeline.

A reusable workflow in practice

Here’s a deploy pipeline defined once in a central repo:

# .github/workflows/deploy.yml in org/ci-templates
name: Reusable Deploy
on:
  workflow_call:
    inputs:
      environment:
        required: true
        type: string
      image_tag:
        required: true
        type: string
    secrets:
      deploy_token:
        required: true

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: ${{ inputs.environment }}   # ties into GitHub's approval gates
    steps:
      - uses: actions/checkout@v4
      - name: Deploy
        run: ./deploy.sh "${{ inputs.image_tag }}"
        env:
          DEPLOY_TOKEN: ${{ secrets.deploy_token }}

Every consuming repo shrinks to a call:

jobs:
  ship:
    uses: org/ci-templates/.github/workflows/deploy.yml@v2
    with:
      environment: production
      image_tag: ${{ github.sha }}
    secrets:
      deploy_token: ${{ secrets.DEPLOY_TOKEN }}

Now the deploy logic lives in one place. Fix it once, bump the tag, done.

Pin to a version, never to a branch

That @v2 is load-bearing. Calling @main means every repo silently inherits whatever you push to main — including a mistake — the instant you push it. That’s the blast-radius problem in reverse: one bad commit breaks every consumer simultaneously, with no staging.

Pin consumers to a tag (@v2) or a SHA. Roll out changes by publishing a new tag and migrating repos deliberately. This gives you a back-out path: if @v3 breaks something, the repo’s one-line revert to @v2 is your rollback. Treat your central workflow repo like the production dependency it is.

Pro Tip: Pin to a full commit SHA, not just a tag, for anything that touches deploy credentials. Tags can be force-moved by anyone with write access to the template repo; a SHA can’t. For a deploy pipeline that holds prod secrets, that immutability is worth the slightly uglier reference.

Approval gates belong in the workflow

Notice environment: ${{ inputs.environment }} in the reusable workflow. GitHub environments let you attach required reviewers, so a deploy to production pauses for human approval before the job runs. Defining this in the reusable workflow means every repo that calls it inherits the gate automatically — you can’t accidentally ship a repo that skipped the approval step, because the gate lives in the shared template, not in each repo’s copy.

This is the right place for the gate: centralized, mandatory, and impossible to forget. Scope deploy secrets to the environment too, so the deploy_token is only available after approval, not to every job in the repo.

Where AI speeds this up

Migrating thirty-eight repos from copy-pasted YAML to a reusable-workflow call is tedious, mechanical, and error-prone — which makes it a perfect fast-junior-engineer task. I use Copilot or Claude to diff each repo’s current pipeline against the canonical one, flag the genuine differences, and draft the replacement call. The model is great at the repetitive transformation.

The judgment stays human. The model will happily “normalize” a repo’s pipeline that legitimately differs — maybe one service really does need an extra migration step — and erase a meaningful difference. So every generated migration goes through review before it merges, the same way I’d review a junior’s PR. The model drafts; a human confirms the behavior is preserved and owns the merge. And it never touches the actual secrets — it works on YAML structure, not credentials. I keep my workflow-refactor prompts in the prompt workspace so they’re consistent and reviewed.

Auditing the shared workflows

Centralizing logic concentrates risk: a vulnerability in the shared workflow is now a vulnerability in every repo. Before publishing a new version of a workflow that holds deploy secrets, I review it for the usual Actions footguns — unpinned third-party actions, pull_request_target misuse, secrets exposed to fork PRs. This is the kind of structured review I route through the code-review dashboard, because a shared deploy workflow deserves more scrutiny than a one-off repo’s YAML.

Keep the interface small

The temptation with reusable workflows is to add an input for every conceivable variation until the workflow has twenty boolean flags and is harder to understand than the copy-paste it replaced. Resist it. A reusable workflow with a small, opinionated interface — environment, image tag, done — is the asset. One with a flag for everything is just distributed complexity. If two callers need genuinely different pipelines, that’s two reusable workflows, not one with a mode: switch.

Conclusion

Reusable workflows and composite actions turn forty copies of drifting CI YAML into one versioned, gated, auditable pipeline. Pin consumers to tags or SHAs for a clean back-out path, define approval gates in the shared workflow so they can’t be skipped, and keep the interface small. Let AI handle the mechanical migration, but review every change like a junior’s PR and keep secrets scoped to environments — never in the model’s hands.

The automation category has more on CI/CD pipelines and safe rollout patterns, and the prompt packs include reviewed templates for refactoring CI YAML without losing behavior.

Newsletter

Free: the DevOps AI Incident-Triage Cheat Sheet

Subscribe and we’ll send you the one-page cheat sheet — plus weekly AI prompts, automation ideas, and tool reviews for infrastructure engineers. One email a week. No spam, unsubscribe anytime.

  • AI Incident-Triage Cheat Sheet (PDF)
  • Access to 1,300+ DevOps AI prompts
  • One practical workflow email per week