BlogCI/CD

DRY GitHub Actions with reusable workflows

Extract shared build-and-scan steps into a reusable workflow and call it from every repo with inputs and secrets.

Jan 14, 2025·4 min readIntermediate·By the SecOpsLog team · command-tested

Fifty repos with fifty nearly identical .github/workflows/ci.yml files is how security policy drifts: one team pins actions/checkout@v4, another stays on v2, and nobody owns the Trivy flags. Reusable workflows in GitHub Actions define workflow_call once — build, test, scan, sign — and caller repos invoke it with inputs and secrets. Fix OIDC trust or a scanner threshold in one repo; bump a tag to roll it everywhere.

You will author a callable workflow in a templates repo, invoke it from an application repo with secrets: inherit, and pin the ref you call. GitLab has includes and components with the same goal; the pattern generalizes across CI systems covered in Secure CI/CD with GitLab.

Caller repo to shared pipeline template

The reusable workflow runs in the caller repo context — it sees the caller checkout and secrets you pass or inherit. Treat the template repo like a dependency with semver.

1Template repobuild-scan.yml + tag v12workflow_callinputs + secrets schema3Caller ci.ymluses: org/templates/...@v14Checkoutcaller code, not template5Build + scanshared steps run6OIDC publishregistry trust once7Bump @v2org-wide policy update

Define the reusable workflow once

Put org-wide steps here: checkout, setup language, build image, Trivy or Grype gate, Cosign sign, SBOM upload. Expose inputs for image name, Dockerfile path, and severity threshold; declare which secrets callers must provide (registry token, Cosign key optional if using OIDC).

.github/workflows/build-scan.yml
name: Reusable build and scan
on:
workflow_call:
inputs:
image:
required: true
type: string
severity:
required: false
type: string
default: HIGH,CRITICAL
secrets:
registry_token:
required: false
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
id-token: write
steps:
- uses: actions/checkout@v4
- run: docker build -t ${{ inputs.image }} .
- uses: aquasecurity/[email protected]
with:
image-ref: ${{ inputs.image }}
severity: ${{ inputs.severity }}
exit-code: 1

Call it from every application repo

The caller workflow stays thin — triggers on push, delegates to the template. secrets: inherit passes repository and organization secrets the reusable workflow declared; alternatively map explicitly so unrelated secrets never leak into the job graph. Document required secrets in the template README so new repos know what to configure before their first green build.

.github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
pipeline:
uses: acme/ci-templates/.github/workflows/[email protected]
with:
image: ghcr.io/acme/api:${{ github.sha }}
severity: HIGH,CRITICAL
secrets: inherit
bash — pin the template like a librarylive
grep "uses: acme/ci-templates" .github/workflows/ci.yml
uses: acme/ci-templates/.github/workflows/[email protected]
bump v1.4.0 → v1.5.0 when the template adds a scanner gate
uses: .../build-scan.yml@main — silent org-wide behavior change

Governance: who can change the template

Restrict write access on the templates repo. Tag releases with changelog notes when scanner flags or action SHAs change. Caller repos should depend on tags or full SHAs — not floating branches. Code review on the template is code review on fifty pipelines at once; that is the point and the risk.

Composite actions solve smaller reuse (a single job step bundle); reusable workflows own entire jobs with their own runner matrix and permissions block. Reach for workflows when you need a standard job — scan gate, deploy to staging, sign and push — not just a shared checkout snippet.

Copy-paste CI vs reusable workflows
Per-repo YAML
Policy drifts silently
Fifty places to bump actions
Inconsistent scan gates
Hard to audit
Reusable workflow
Single source of truth
Semver bump rolls forward
Shared OIDC + signing
Review once, apply many
Pin the ref you call
Calling @main means an upstream change alters every repo on the next run — including how your secrets are used. Pin to a tag or SHA and bump deliberately. The reusable workflow executes with caller credentials; a compromised template is a supply-chain incident.

Where this goes next

Reuse gets you consistent gates; supply-chain maturity adds SBOM archival, provenance attestation, and admission policies that verify signatures. Wire Cosign and SLSA-style provenance into the same template so security wins ship with semver. Software supply chain security covers scanning, signing, and policy end to end.

Go deeper in a courseSoftware supply chain securityPipeline reuse, pinning, SBOMs, and org-wide secure defaults.View course

Related posts