Chapter 18Lesson 03~165 minutes

Reusable Workflows, Composite Actions, JavaScript Actions, and Automation Reuse: Configuration, Design Choices, and Tradeoffs

A shared automation component is platform code. The design question is not “which syntax is shorter?” but which unit should own jobs, runner selection, permissions, secrets, distribution, runtime dependencies, and release cadence. This lesson makes those choices explicit and shows when centralization reduces drift versus when it creates a large blast radius.

Design tradeoffsCentralizationVersion policyLeast privilege

Learning objectives

  • Choose reusable workflow, composite action, or JavaScript action based on orchestration boundary and runtime needs.
  • Choose repository-local versus centralized automation based on ownership, blast radius, access policy, and release cadence.
  • Design immutable-pin and readable-version-tag update workflows without treating mutability as an implementation detail.
  • Prefer explicit permissions and short-lived/OIDC capabilities over broadly inherited secrets where the task permits.
  • Evaluate maintainability, security, governance, reliability, compatibility, and cost in a worked production scenario.

Availability: All core design exercises are free-compatible. Organization/enterprise centralization, private/internal repositories, allowlists, and enterprise Actions policies are optional architecture extensions. Their exact availability and administrative authority depend on account/deployment policy.

1. Reusable workflow versus composite action versus JavaScript action

Start with the boundary of change. If a platform team needs to standardize an entire CI stage—runner labels, jobs, token permissions, matrices, artifact policy—the reusable workflow is the natural control point. If application teams own the job but repeat a small ordered sequence, a composite action keeps the caller topology visible. If a step needs complex program logic, robust parsing, API clients, unit tests, or cross-platform behavior, a JavaScript action can provide a better software abstraction.

Question Reusable workflow Composite action JavaScript action
Owns jobs/runners? Yes No No
Runs as caller step? No — called as job Yes Yes
Can naturally centralize permissions? Yes, within caller ceiling Uses caller job permissions Uses caller job permissions
Runtime packaging Workflow YAML + referenced actions Metadata + steps/scripts Metadata + packaged JS/dependencies
Best for Policy/orchestration Repeated step recipes Reusable program logic

2. Repository-local reuse versus centralized platform automation

Local components version together with the application. That makes changes atomic and rollback simple: one commit identifies application plus automation. The cost is duplication across repositories. Central components eliminate duplication and enable platform-wide controls, but now every release of the component has a blast radius and access path.

A central workflow repository should therefore have owners, protected changes, compatibility tests, release notes, canary callers, deprecation policy, and an inventory of consumers. Avoid one giant “platform workflow” with dozens of conditional modes; smaller stable interfaces make ownership and rollback clearer.

Private sharing boundary: When private actions/workflows are shared across repositories, GitHub grants the runner a short-lived scoped read token for the component repository and warns about indirect visibility through caller logs. Treat central private automation as shared code, not as a secret vault.

3. Immutable SHA pins and readable version tags solve different update problems

A full commit SHA maximizes reproducibility: a run can always identify the exact reviewed Git object. The downside is update friction; consumers do not receive fixes until they intentionally change the pin. A version tag such as v3 is readable and enables maintainer-managed updates, but it is mutable. A branch is even more mutable and usually unsuitable for production dependencies.

A practical policy is review-by-SHA, automate update proposals: platform maintainers publish releases; a bot or scheduled process proposes pin updates to callers; CI validates the new pin; owners review release/source diffs; merge updates deliberately. For internal reusable workflows, tags can remain a convenience channel, but production callers that need reproducibility should still prefer exact SHAs when using cross-repository references.

4. Same-repository reuse is versioned by the caller commit

When the caller uses ./.github/workflows/c18-reusable.yml or ./.github/actions/c18-normalize, no external @ref is needed. Those files come from the caller repository revision selected for the run. This is both simple and atomic: changing the application and its local automation can be reviewed in one pull request.

The tradeoff is that local reuse does not centralize fixes across repositories. Use it when coupling to the application is stronger than the benefit of central release management.

5. Permission design: caller grants capability; shared code narrows or uses it

Reusable workflows should document required token permissions as part of their interface. The caller should grant only those permissions at the call job. Nested workflows cannot increase GITHUB_TOKEN authority, so a platform workflow cannot silently turn contents: read into contents: write.

permissions: {}
jobs:
  release-evidence:
    permissions:
      contents: read
      attestations: write
      id-token: write
    uses: platform/automation/.github/workflows/evidence.yml@FULL_REVIEWED_SHA

The example is conceptual: grant only what the called workflow actually needs. Do not add blanket write permissions because a shared component “might need it later.” A new permission requirement is an interface change that callers should review.

6. Named secrets versus inherited secrets versus OIDC

Named secrets make dependency surfaces visible. secrets: inherit reduces YAML but broadens the set of capabilities a shared workflow can see and makes later secret additions silently available. For cloud deployment, a reusable workflow often does not need a stored cloud key at all: a caller can grant id-token: write and the workflow can use OpenID Connect to obtain a short-lived cloud credential, subject to cloud-side trust policy.

OIDC is not “permissionless.” It moves authorization to the cloud identity policy and shortens credential lifetime. Chapter 20 covers that mechanism in depth; here the design rule is to prefer explicit, task-scoped capabilities over broad secret inheritance.

7. Third-party actions: control source, identity, and update path

Repository/organization Actions policy can limit which actions and reusable workflows are allowed. GitHub also supports policies that require full-length commit SHA pinning for actions. These platform controls are valuable guardrails, but they do not replace source review. A pinned malicious commit is perfectly reproducible.

Build an approved-component catalog containing canonical repository, owner, purpose, reviewed SHA, release/tag association, required permissions/secrets/network access, last review date, and update owner. Reject renamed/redirected dependencies: GitHub intentionally does not support redirects for actions/reusable workflows, so repository renames can break callers and should trigger explicit migration review.

8. JavaScript action maintenance includes generated code and runtime compatibility

JavaScript actions separate caller workflow syntax from implementation, but the repository becomes a software package. Maintain src, dependency lockfile, tests, action metadata, and the committed distributable bundle. Reviewers need tooling that proves the bundle is regenerated from the reviewed source. Dependabot or another dependency process should cover the action repository like any other production Node project.

Runtime selection in action.yml must also remain compatible with GitHub.com and the GHES versions you support. Do not hard-code GitHub.com API URLs in custom actions; use GITHUB_API_URL/GITHUB_GRAPHQL_URL or the Actions toolkit so GHE.com/GHES callers can resolve their own endpoints.

9. Worked scenario: 80 repositories need standardized CI

Assume an engineering organization has 80 services. Every service needs checkout, language-specific unit tests, SBOM generation, and one policy check. Teams also need a small normalization routine inside several jobs.

Decision dimension Chosen approach Why
Maintainability Central reusable workflow + small composite action One CI policy implementation; step helper remains independently reusable
Security Caller-declared least privilege; external actions pinned to reviewed SHAs Shared code cannot silently gain caller authority; dependencies are identifiable
Governance CODEOWNERS + release notes + canary callers + consumer inventory Central change has explicit owner/blast radius
Reliability Versioned releases; callers update through PRs Rollback is a pin change, not emergency editing in 80 repos
Compatibility Stable typed inputs; deprecation window for renamed inputs Interface evolution is deliberate
Cost Avoid duplicated maintenance; keep runner selection appropriate Reuse itself does not eliminate compute; bad central topology can multiply cost

The organization does not put every application-specific step into the central workflow. Service-specific commands remain inputs or caller-owned steps where that preserves clear ownership.

10. Design checklist before extracting shared automation

  • Is the duplicated behavior actually stable enough to share?
  • Does it need to own jobs/runners, or only repeated steps?
  • What inputs, outputs, permissions, secrets, variables, and network destinations form the interface?
  • Who owns backward compatibility and release notes?
  • How will callers pin, discover, and update versions?
  • What happens when the central component is unavailable or broken?
  • How will we know which repositories consume each version?
  • Can a compromised component reach secrets or write-scoped tokens across many callers?

11. Lesson summary

Choose reuse based on execution boundary, not line count. Local reuse gives atomic versioning with the application; central reuse trades local autonomy for consistency and a larger blast radius. Full SHAs maximize reproducibility, named permissions/secrets make capabilities visible, and JavaScript actions require software-package maintenance. Shared automation should have owners, releases, compatibility policy, tests, and consumers just like any internal platform API.

Knowledge check

A central workflow needs a new packages: write permission. Is that an internal implementation detail?

When is repository-local composite reuse preferable to a central action repository?

Why can a full SHA pin still be unsafe?

Why prefer named secrets over secrets: inherit for shared platform workflows?

What is the operational cost of centralizing automation?

Next lesson

Next: Reusable Workflows, Composite Actions, JavaScript Actions, and Automation Reuse: Diagnostics, Failure Modes, Security, and Performance

Further reading — current official GitHub sources

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.