GitHub Actions reusable workflows let teams centralize repeated CI/CD jobs while keeping each repository responsible for its application-specific choices. This guide provides a practical checklist for designing, securing, versioning, and troubleshooting shared workflows without turning every repository into an identical pipeline.
Overview
A reusable workflow is a complete GitHub Actions workflow that another workflow calls as a job. The called workflow is usually stored in a central repository, commonly under .github/workflows/, and begins with the workflow_call trigger. The caller then references it with the uses key at the job level.
This model is useful when several repositories need the same build, test, security, packaging, or deployment sequence. Instead of copying YAML into every repository, a platform or release engineering team can maintain one implementation and expose controlled inputs such as runtime version, deployment target, or artifact name.
Centralization is not automatically better. A shared workflow creates a dependency between the template owner and every caller. The most maintainable design separates stable engineering policy from repository-specific configuration. For example, the reusable workflow can enforce that tests run and artifacts are retained, while the caller supplies the application path or a supported deployment environment.
Use reusable workflows for multi-job processes that need a consistent interface. Use a broader CI/CD platform comparison when you are still deciding whether GitHub Actions fits your operating model. For a team adopting the approach across many services, document it alongside your internal developer platform standards.
Reusable workflows, composite actions, and starter workflows
| Approach | Best for | Key limitation |
|---|---|---|
| Reusable workflow | Sharing complete jobs or multi-job CI/CD processes | Callers must use the defined inputs, secrets, and permissions interface |
| Composite action | Packaging a sequence of steps as one action | It does not replace a complete workflow or provide the same job-level structure |
| Organization-wide starter workflow | Giving new repositories a useful initial workflow | It is a starting template, not a centrally updated runtime dependency |
A starter workflow is appropriate when repositories need a recommended beginning but are expected to evolve independently. A composite action is useful for a repeated step sequence, such as installing a tool or producing a standardized report. A reusable workflow is the stronger choice when the organization needs a controlled pipeline contract.
Checklist by scenario
1. Before creating the shared workflow
- Define the repeated outcome. Write down whether the workflow builds, tests, scans, publishes, deploys, or performs several of these tasks.
- List the stable interface. Identify the inputs that callers genuinely need, such as
node-version,working-directory,artifact-name, ordeploy-environment. - Separate policy from preference. Required checks and security controls belong in the shared workflow. Optional framework flags and application-specific commands may belong in the caller.
- Choose an ownership model. Assign maintainers, a review path, and a change-notification method before other repositories depend on the workflow.
- Decide the compatibility boundary. A workflow intended for multiple languages or deployment targets may need a small number of explicit modes rather than a large collection of loosely documented switches.
2. Designing the called workflow
A minimal reusable workflow declares inputs and secrets under on.workflow_call. For example:
name: Shared test pipeline
on:
workflow_call:
inputs:
working-directory:
required: false
type: string
default: .
secrets:
package-token:
required: false
jobs:
test:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- name: Install dependencies
working-directory: ${{ inputs.working-directory }}
run: ./scripts/ci-install.sh
- name: Run tests
working-directory: ${{ inputs.working-directory }}
run: ./scripts/ci-test.shUse typed inputs where supported and give every input a clear default or mark it required. Keep names descriptive and avoid passing a single unstructured JSON object unless the interface truly needs it. A small, explicit contract is easier to review and less likely to break callers silently.
3. Calling the workflow
The caller invokes a reusable workflow as a job, not as an individual step:
name: Service CI
on:
pull_request:
push:
branches: [main]
jobs:
shared-tests:
uses: platform-ci/.github/.github/workflows/shared-tests.yml@v1
with:
working-directory: services/api
secrets:
package-token: ${{ secrets.PACKAGE_TOKEN }}
permissions:
contents: readKeep the caller readable. It should show the repository trigger, the selected shared workflow version, the important inputs, and any permissions or environment decisions that affect the run. Avoid hiding application-specific behavior in undocumented conventions.
4. Passing secrets safely
- Declare only the secrets the called workflow needs.
- Pass named secrets explicitly when the interface is stable and auditable.
- Use
secrets: inheritonly when the trust boundary is clear and the called workflow is controlled by the same organization or security model. - Never pass a secret as a normal input or print it while debugging.
- Prefer short-lived credentials, workload identity, or an approved secrets manager where the deployment platform supports them.
- Document which repository, organization, or environment must own each secret.
For a broader review of secret storage and rotation, use the DevSecOps secrets management guide. Reusable workflows reduce duplication, but they do not remove the need to audit who can change the called workflow and who can invoke it.
5. Setting permissions deliberately
Start with restrictive permissions and add only what the job requires. A build that checks out private source code may need contents: read; a release job may require additional access, but that should be visible and justified. Set permissions at the workflow or job level according to the narrowest useful scope, and do not assume that a called workflow should receive every permission available to the caller.
Review permissions whenever the workflow adds a new action, publishes a package, creates a release, comments on a pull request, or interacts with cloud infrastructure. Permission behavior can also depend on the event that started the run, so test pull requests from forks and other less-privileged contexts separately.
6. Handling environments and deployments
Keep deployment approval and environment selection explicit. A called workflow can define a deployment job that targets an environment, while the caller supplies a validated environment name or selects a controlled deployment path. Confirm where environment protection rules and environment-scoped secrets are configured. Do not assume that passing repository secrets through workflow_call automatically makes environment secrets available; verify the behavior of the exact job and environment design you intend to use.
For production deployments, add a visible gate between build and release where appropriate, use immutable artifacts, and record the commit or version being deployed. If the deployment changes routing or certificates, coordinate the workflow with your DNS and hosting process.
7. Versioning the shared workflow
Do not make every caller depend silently on a moving branch. Publish an intentional versioning scheme, such as reviewed tags or immutable commit references, and explain how consumers upgrade. A major version can represent a breaking interface change; a minor or patch-level convention can communicate compatible improvements if the team can maintain that discipline.
Pin third-party actions according to your security policy. A full commit SHA provides a stronger immutability guarantee than a floating tag, while a reviewed major tag may be easier to operate. Whichever model you choose, record why it was chosen, review updates, and avoid mixing undocumented pinning styles across repositories.
8. Troubleshooting a caller failure
- Confirm that the referenced workflow exists at the expected path and is available to the caller.
- Check that the called file declares
workflow_calland that the caller uses it at the job level. - Compare every input name, type, and required setting with the called workflow’s contract.
- Verify that secrets are declared, passed, and available in the event context.
- Inspect effective permissions for both the caller and called jobs.
- Check environment names, protection rules, approvals, and environment-scoped credentials.
- Review action versions, runner labels, working directories, and artifact names.
- Reproduce the failure with a small test repository or a non-production environment before changing the shared workflow for everyone.
For a broader diagnostic sequence, keep the CI/CD pipeline troubleshooting checklist with your team’s runbooks.
What to double-check
- Interface: Are inputs, outputs, and secrets documented with examples and safe defaults?
- Outputs: If the caller needs an artifact ID, image digest, test result, or deployment URL, is it exposed as a declared workflow output rather than inferred from logs?
- Permissions: Does each job request only the access it needs, and have pull requests from forks been considered?
- Trust: Can an untrusted contribution influence a privileged deployment job or alter a command that receives credentials?
- Reproducibility: Are actions, dependencies, containers, and artifacts versioned well enough to investigate a later failure?
- Observability: Do logs clearly identify the repository, commit, workflow version, environment, and deployment target without exposing secrets?
- Rollback: Can a caller return to the previous workflow version, and can a deployment restore a known-good artifact?
- Ownership: Is there a maintainer and an upgrade path when GitHub Actions syntax, runner behavior, or organizational release standards change?
Successful pipeline design also depends on reliable feedback after deployment. Connect workflow results to the team’s operational process and review them with service objectives and incident practices, such as those described in the SLI and SLO guide.
Common mistakes
Copying instead of defining a contract
Copying a working workflow into every repository is easy at first, but fixes then require coordinated edits. Start with a shared contract and keep local overrides intentional. If two repositories differ for legitimate reasons, expose that difference as a documented input rather than relying on hidden branch names or directory conventions.
Making the shared workflow too generic
A workflow with dozens of flags becomes difficult to test and explain. Split unrelated responsibilities, such as build and production deployment, when their security boundaries or release cadence differ. A reusable workflow should simplify a common path, not model every possible repository.
Using broad secret inheritance by default
Convenience can obscure the credential boundary. Named secret passing makes reviews clearer and helps the called workflow evolve without accidentally gaining access to unrelated credentials.
Changing the default branch without a migration plan
A breaking change to inputs, outputs, permissions, runner requirements, or artifact formats can affect many repositories at once. Publish a new version, test representative callers, communicate the migration, and keep the previous version available for a defined transition period.
Debugging only from the template repository
A workflow may pass in its own repository and fail in a caller because of different permissions, secrets, event types, environments, or repository layout. Test from representative caller repositories, including the least-privileged and most sensitive cases.
When to revisit
Review reusable workflows before seasonal planning or release-standard reviews, when new repositories are onboarded, and whenever the organization changes its deployment environments or security requirements. Also revisit the design after a failed deployment, a permissions audit, a runner migration, or the introduction of a new language, package registry, cloud account, or artifact format.
Use this short maintenance routine:
- List every repository and team using each shared workflow version.
- Review recent failures for recurring interface, permissions, or environment problems.
- Check action and dependency updates against your pinning policy.
- Confirm that secrets, environment approvals, and cloud identities still match the intended trust boundary.
- Test the workflow with a representative application and a pull request from a restricted context.
- Publish migration notes before changing required inputs or behavior.
- Retire obsolete versions only after callers have moved and rollback expectations are documented.
When adding a new repository, begin with the workflow contract, a minimal caller, and a test deployment path. When upgrading an existing repository, change one shared-workflow version at a time and record the result. This keeps reusable CI/CD pipelines centralized enough to govern, but flexible enough for real engineering teams.