Reusable Pipelines: Share the Recipe, Keep the Boundaries
Use Azure YAML templates, variable groups, and GitHub reusable workflows without spreading secrets or accidentally increasing permissions.
Share a recipe instead of photocopying it twenty times
Copying a build sequence into twenty repositories makes the first rollout fast and every later correction slow. Reuse centralizes the recipe, but a bad central change can affect all consumers. Reusability therefore needs interfaces, versioning, testing, and ownership.
Separate stable behavior from consumer choices. A template can own restore/test/publish structure while parameters select the supported runtime or build target. Do not turn every shell command into an unrestricted parameter; that can make the βstandardβ template an arbitrary execution tunnel.
An Azure steps template
In a disposable project with the relevant npm scripts, a template such as templates/node-checks.yml can declare its interface:
parameters:
- name: runTests
type: boolean
default: true
steps:
- script: npm ci
displayName: Restore locked dependencies
- ${{ if eq(parameters.runTests, true) }}:
- script: npm test
displayName: Test
A consuming YAML pipeline can reference it:
steps:
- template: templates/node-checks.yml
parameters:
runTests: true
This is a reuse demonstration, not a policy allowing release consumers to skip required tests. If tests must never be disabled, do not expose that choice in the production interface.
Templates can operate at step, job, or stage level. An extends design can impose broader structure. Know what is evaluated during template expansion versus runtime; a compile-time parameter cannot simply read a value that only a later job will produce.
Parameters, variables, and secrets are not interchangeable
| Mechanism | Typical purpose | Important caution |
|---|---|---|
| Typed template parameter | Choose structure or a validated input | Do not use for secrets |
| Pipeline variable | Runtime configuration and values | Expansion timing and scope matter |
| Variable group | Share controlled variables across pipelines | Authorize consumers; secret values remain sensitive |
| Secret store integration | Retrieve protected deployment material | Access to the store and pipeline must both be controlled |
Azure macro variables such as $(name) and template expressions such as ${{ parameters.name }} have different evaluation timing. An assistant that mixes them may produce YAML that looks plausible but selects the wrong value.
Variable groups are not a reason to grant every pipeline access to production credentials. Review library permissions, pipeline authorization, approvals/checks for protected resources, and Key Vault linkage where used.
Task groups are associated with classic pipelines, while YAML pipelines use templates for comparable reuse. A task group is not dropped into YAML as though it were a YAML template. Understand the existing delivery model before choosing a reuse mechanism.
GitHub reusable workflow boundaries
A reusable workflow declares workflow_call and defines explicit inputs and secrets:
name: Reusable verification
on:
workflow_call:
inputs:
runtime:
type: string
required: true
permissions:
contents: read
jobs:
verify:
runs-on: ubuntu-latest
steps:
- run: echo "Consumer selected runtime"
The example shows the interface only; it deliberately does not install or test a runtime. A real shared workflow must validate and use the selected input with reviewed actions and commands.
Call reusable workflows at the job level with uses, rather than treating them as a single step action. A composite action is a different reuse unit. Pin a reviewed commit when the trust model requires an immutable reference.
Nested reusable workflow permissions can be maintained or reduced, not elevated beyond what the caller grants. A deeper callee cannot repair missing caller permission by requesting a broader token. Secrets also need deliberate propagation; do not assume every nested workflow automatically receives everything.
Make central changes safe
Test a new shared version with representative consumers, including a deliberate failing input and a failing test. Review generated logs and permission behavior. Publish a migration path rather than silently changing the meaning of an existing input.
Pinning prevents surprise updates but creates an update obligation. A shared template at a moving branch gets fixes faster but has a broader change radius. Choose explicitly and document the expected update process.
A nested reusable GitHub workflow requests write access, but its caller granted only read access. What is true?
Try it: review the interface before the implementation
Prerequisites: a sandbox YAML repository and optional CI execution rights. CI minutes may be billed.
Create a small non-secret template input with a bounded set of allowed meanings. Call it from two sandbox consumers. Test one normal value and one invalid value. Inspect whether a consumer can disable a control you intended to make mandatory.
Expected observation: the interface is understandable without reading every implementation line, and invalid input fails explicitly rather than selecting a dangerous default.
Cleanup: remove the sandbox consumers/template or retain them only as clearly labeled examples. Do not grant a real production variable group to complete the exercise.
Decision check: should every parameter be configurable?
No. Expose meaningful supported variation. A parameter that lets a consumer remove mandatory security or quality controls defeats the shared policyβs purpose.