Modularizing CI/CD with GitHub Actions Reusable Workflows
Reduce duplication and centralize CI logic across projects by using GitHub Actions reusable workflows. Learn how to build, version, and secure a shared workflow, and see a concrete example that works across two repos.
05 Jul 2026, 12:26 UTC

Problem: Duplication in CI/CD Pipelines
Most teams maintain a separate .github/workflows directory in every repository. When a new test step or linting rule is added, it must be copied and updated in every repo. The result is a maintenance burden and a higher chance of drift between pipelines.
Thesis: Reusable Workflows Centralize Logic
GitHub Actions supports reusable workflows, a YAML file that lives in one repository and can be invoked by many others. The caller repo does not need to duplicate steps; it simply references the shared file and passes parameters. This keeps the pipeline definition in a single place, improving consistency and reducing the number of moving parts.
How Reusable Workflows Work
A reusable workflow is a standard Actions file that starts with a on: section using the workflow_call trigger. The caller supplies inputs and the workflow runs in the context of the calling repository, inheriting its permissions and environment variables. The caller can also expose secrets to the workflow by declaring secrets: in the call.
Building a Minimal Reusable Workflow
Store the shared logic in a dedicated repository, e.g., org/shared-ci. The workflow file might look like this:
name: Shared CI
on:
workflow_call:
inputs:
node-version:
required: true
type: string
secrets:
NPM_TOKEN:
required: false
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
registry-url: "https://registry.npmjs.org/"
scope: "@myorg"
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: Publish (optional)
if: ${{ secrets.NPM_TOKEN }}
run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
Key points:
- Parameters are defined under
inputsand can be overridden by the caller. - Secrets are explicitly listed; if a caller doesn’t provide one, the step that requires it can be gated with
if. - The workflow runs with the permissions of the caller, so any
GITHUB_TOKENactions will have the caller’s access.
Consuming the Reusable Workflow
In the calling repository, create a wrapper workflow that simply references the shared file. For example, in repo-a:
name: CI
on: push
jobs:
shared-ci:
uses: org/shared-ci/.github/workflows/shared-ci.yml@v1
with:
node-version: "18"
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
Where to run the commands:
- Commit and push the wrapper to trigger the shared workflow.
- Ensure the
workflowpermission is enabled for the repo (Settings → Actions → Repository permissions).
Verification: open the Actions tab in repo-a, click the latest run, and confirm the steps from the shared workflow appear. The logs will show the values of inputs and any secrets that were used.
Versioning and Backward Compatibility
Use Git tags or branch names to pin the shared workflow. In the example above, @v1 refers to the v1 tag in org/shared-ci. When you need to change the workflow, create a new tag (e.g., v2) and update callers to reference it. This preserves older pipelines while allowing newer ones to adopt the updated logic.
Commands to tag:
git tag v2
git push origin v2
Check that the tag points to the desired commit by running git show v2 locally or inspecting the tag on GitHub.
Security & Permissions
Reusing workflows introduces a shared boundary:
- Caller repositories must grant the
workflowpermission; otherwise the call will fail with a 403 error. - Secrets from the caller are the only ones that can be injected. The shared workflow cannot read secrets it does not receive.
- If a caller accidentally exposes a secret in a public repo, it will be available to the shared workflow.
Mitigation steps:
- Restrict the
workflowpermission to trusted repos only. - Audit the list of secrets in each repo and use the
secretsfilter in the reusable workflow to avoid leaking unused secrets. - Use
if: ${{ secrets.SOME_SECRET }}to guard steps that require them.
Concurrency & Cost Considerations
GitHub imposes a concurrency limit per account (currently 20 concurrent jobs for free accounts). If many repos trigger the same reusable workflow simultaneously, jobs will queue, potentially delaying feedback. The cost is proportional to runtime; a large reusable workflow can increase the total number of steps executed across all repos.
Practical check:
- Navigate to Settings → Actions → Usage to view the number of queued jobs.
- Use the
concurrencykey in the wrapper to cancel older runs:
concurrency:
group: "repo-a-ci"
cancel-in-progress: true
This ensures only the latest push triggers a run, reducing queue time.
Trade‑off: Centralized vs. Local Flexibility
Centralizing logic has clear benefits: a single source of truth, easier updates, and consistent behavior. However, it can reduce flexibility for projects that need custom steps or different toolchains. In such cases, you can still extend the shared workflow with additional jobs or steps in the caller’s wrapper, or create multiple reusable workflows for different scenarios.
Actionable Checklist
- Create a dedicated repo for shared workflows.
- Define a
workflow_callworkflow with inputs and secrets. - Tag the workflow (e.g.,
v1) and push to GitHub. - In each caller repo, enable the
workflowpermission. - Reference the shared workflow using
uses: org/repo/.github/workflows/file.yml@v1. - Pass required inputs and secrets via
withandsecrets. - Verify execution in the Actions UI; check logs for input values.
- Monitor concurrency and cost in the Actions usage dashboard.
- When updating the shared workflow, create a new tag and update callers as needed.
Closing
Reusable workflows let teams keep CI/CD pipelines lean and maintainable. By centralizing logic, you reduce duplication, enforce consistency, and simplify updates. Just remember to manage permissions, version tags, and concurrency limits carefully. With the checklist above, you can start pulling your repositories together under a single, versioned CI contract today.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.