Workflow Triggers, Filters, Expressions, Contexts, Variables, Inputs, and Outputs: Configuration, Design Choices, and Tradeoffs
More triggers and variables do not automatically make automation more flexible; they often make its behavior harder to prove. This lesson focuses on design ownership: deciding which layer should filter work, which data class should hold a value, and when an output is a better contract than a file artifact or shared mutable state.
Learning objectives
- Choose between automatic event triggers, manual dispatch, reusable workflows, and external repository dispatch based on ownership and trust.
- Choose between scheduler path filters and in-workflow conditions without creating missing required checks or unnecessary compute.
-
Classify values among
vars,env,inputs,secrets, outputs, and artifacts. - Distinguish output contracts for small metadata from artifacts/files and explain their reliability/security implications.
- Use a worked decision table to justify maintainability, security, reliability, compatibility, and cost.
Availability: All core decisions are learnable on GitHub.com Free/public repositories. Organization-level variables/secrets and environment policy vary by ownership, visibility, and plan; reusable-workflow access also depends on repository visibility and organization policy. The mandatory exercises do not require paid features.
1. Event trigger versus manual/reusable/external invocation
Choose a trigger by asking who owns the cause. If
source change should always validate itself, use a repository event
such as push or pull_request. If a human
operator should decide when to run, use
workflow_dispatch with a typed input schema. If another
workflow owns the orchestration, use workflow_call. If
an external system owns the cause,
repository_dispatch can represent that event—but its
payload is external input and the run still uses the workflow on the
default branch.
| Choice | Good fit | Primary governance question |
|---|---|---|
| Repository event | CI, issue automation, release response | Can an untrusted actor cause this event, and what permissions/secrets will the run get? |
| workflow_dispatch | Operator-approved maintenance, diagnostics, controlled release step | Who has write access to trigger it, and are inputs constrained enough? |
| workflow_call | Shared reusable validation/deployment unit | Who owns the called workflow version and its input/output contract? |
| repository_dispatch | External build/test system or service integration |
Who can authenticate to create the dispatch, and how is
client_payload validated?
|
| schedule | Periodic maintenance/reporting | Is default-branch latest state the intended source, and what happens if execution is delayed? |
2. Path filters versus in-job conditional logic
Scheduler filters prevent the run from being created. In-job conditions create the run and then skip selected jobs/steps. The former can reduce noise/compute; the latter leaves visible evidence and lets you publish a stable check name. That difference matters when branch protection requires a check.
| Requirement | Prefer | Reason |
|---|---|---|
| Ignore a documentation-only workflow entirely | Path filter | No downstream governance requires a run/check. |
| Required CI check must always resolve | Workflow triggers broadly; job logic narrows expensive work | Avoid a filter-skipped required check remaining Pending. |
| Only release branches are relevant | Branch filter | Eligibility itself is part of policy. |
| Run lightweight validation always, expensive tests conditionally | Job/step if: |
One run preserves full decision evidence. |
Do not optimize Actions minutes by weakening the observability contract. First design the required evidence, then avoid unnecessary jobs within that contract.
3. vars versus env versus inputs versus secrets
| Mechanism | Owner / lifecycle | Sensitive? | Best use |
|---|---|---|---|
env |
Workflow source code; workflow/job/step scope | No | Static workflow-local defaults and shell environment. |
vars |
Repository/org/environment configuration | No; values can be unmasked | Operational non-sensitive config reused across workflows. |
inputs |
Caller/operator supplied per invocation | Treat as untrusted unless caller/control proves otherwise | Parameters such as mode, target, Boolean feature choice. |
secrets |
Repository/org/environment secret store | Yes | Credentials or sensitive values when no stronger short-lived identity is available. |
GITHUB_TOKEN |
GitHub mints per workflow job | Credential | Repository API authentication with explicit least privilege. |
“Not printed” does not make an env or
vars value secret. Conversely, “stored in secrets” does
not make arbitrary execution safe: code that can read a secret can
intentionally exfiltrate it. Secret availability and runner/code
trust must be designed together.
4. Outputs versus artifacts: metadata contract or file transfer?
Outputs are small strings attached to steps/jobs/reusable workflows and evaluated as part of the run graph. They are ideal for a version, digest, Boolean-like decision, artifact identifier, or generated name. Artifacts are stored files with retention, download, integrity, and permission concerns. Do not encode a binary or large JSON report into an output because it avoids learning artifact handling.
| Need | Mechanism | Why |
|---|---|---|
Pass image_digest to deploy job |
Job output | Small scalar, direct dependency contract. |
| Pass compiled binary to test/deploy | Artifact/package | File identity and retention matter. |
| Share mutable “current mode” across jobs | Avoid global mutable state; derive/output explicitly | Reproducibility and causality are clearer. |
| Expose reusable workflow result to caller | workflow output mapped from job output | Explicit API contract across workflow boundary. |
5. Typed inputs are UX and policy, not authorization
A choice input reduces typo space; a Boolean avoids
string truthiness ambiguity; an environment input can select a
GitHub environment. But input type does not authorize the action.
Authorization comes from who may trigger the workflow,
repository/environment policy, job permissions, and external-service
identity. A caller who can choose production still must
not automatically gain deployment authority.
on:
workflow_dispatch:
inputs:
target:
type: choice
options: [staging, production]
required: true
jobs:
deploy:
# Input chooses intent; environment policy governs authority.
environment: ${{ inputs.target }}
runs-on: ubuntu-latest
Environment governance is covered in Chapter 19. Here the key principle is separation: parameters describe requested behavior; permissions/protection rules decide whether the behavior is allowed.
6. Reusable workflows are versioned APIs
workflow_call turns a workflow file into a callable
interface. Inputs and outputs are a schema. A caller can reference a
reusable workflow from the same repository or an allowed external
repository according to GitHub’s access rules. Treat the reusable
workflow reference as a supply-chain dependency: production callers
should deliberately select and review the version/reference they
execute.
# Called workflow
on:
workflow_call:
inputs:
test_level:
type: string
required: true
outputs:
report_id:
value: ${{ jobs.validate.outputs.report_id }}
Do not use secrets as ordinary inputs.
workflow_call has a separate secret contract, and
nested reusable workflows require explicit forwarding/inheritance
choices.
7. Worked scenario: Atlas Edge validation and release planning
Atlas Edge has a required PR validation, an optional expensive integration suite, a nightly dependency report, and a reusable release-preflight unit. Choose the control for each requirement.
| Requirement | Decision | Maintainability / security / reliability rationale |
|---|---|---|
| Every PR gets a required validation result | pull_request workflow broadly triggered |
Stable evidence; untrusted PR data treated as data; read-only token. |
| Integration suite only for service changes | Conditional job based on a controlled change detector rather than filtering out the whole required workflow | Required check still resolves; expensive compute is conditional. |
| Nightly dependency report | schedule on default-branch workflow |
Time owns activation; result identity is current default-branch state. |
| Release-preflight shared across repositories | Reviewed reusable workflow with typed input/output schema | One maintained implementation; versioned dependency; centralized fixes. |
| External QA system requests a repository-side smoke test |
Optional repository_dispatch with narrow
token/app and validated event type/payload
|
Explicit external integration boundary rather than scraping UI. |
Cost: avoid trigger storms and unnecessary jobs. Compatibility: verify GHES version before assuming newest GitHub.com syntax such as current typed-input or schedule features. Security: inputs and event payloads do not become trusted because they arrive through a structured context.
8. Anti-patterns and safer replacements
-
Anti-pattern: store a token in
vars. Replace: secret or short-lived OIDC/App token, least privilege. - Anti-pattern: use path filters on a required workflow without testing check behavior. Replace: stable check-producing workflow and conditional expensive jobs.
-
Anti-pattern: parse a Boolean input as the string
"true"everywhere. Replace: use typedinputscontext. - Anti-pattern: pass large build results as outputs. Replace: artifact/package mechanisms with explicit identity.
- Anti-pattern: let an external dispatch payload choose a shell command. Replace: allowlist typed event names/values and map them to fixed commands.
9. Lesson summary
The design goal is not fewer YAML lines; it is a smaller number of explicit contracts. Event contracts govern activation, variable classes govern configuration ownership, secret boundaries govern sensitive data, and output contracts govern forward data flow. A production workflow should make each contract reviewable without executing it.
Knowledge check
Why can a path filter be the wrong optimization for a required CI workflow?
If the filter prevents the workflow from running, the expected required check can remain Pending and block the PR. Keep the required check contract stable and condition expensive work inside the run when appropriate.
Does a choice input authorize production
deployment?
No. It constrains caller-provided intent. Repository/environment policy, token permissions, and external identity authorize the operation.
When should a value be a job output instead of an artifact?
When it is small metadata needed by dependent jobs, such as a digest, version, ID, or decision. Files belong in artifact/package mechanisms.
Why treat a reusable workflow reference like a dependency?
The called workflow contains executable automation. Its version/source can affect security and behavior, so callers need deliberate ownership/versioning/review.
Can configuration variables safely contain passwords if no step prints them?
No. Variables are non-sensitive configuration and are not masked. Use a secret or stronger short-lived identity mechanism.
Further reading — current official GitHub sources
- GitHub Docs — Workflow syntax
- GitHub Docs — Events that trigger workflows
- GitHub Docs — Expressions
- GitHub Docs — Contexts reference
- GitHub Docs — Variables
- GitHub Docs — Pass job outputs
- GitHub Docs — Script injections
- GitHub Docs — Secure use reference
- GitHub CLI — gh workflow run
- GitHub CLI — gh run list
- GitHub Docs — Reuse workflows
- GitHub Docs — Variables concepts
- GitHub Docs — Artifacts concepts
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.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.