Chapter 02Lesson 03~105 minutes

Workflow Files, YAML Structure, Jobs, Steps, and Actions: Configuration, Design Patterns, and Trade-Offs

Once a workflow runs, the next question is whether its structure will remain understandable, secure, and maintainable. This lesson treats workflow shape as architecture. You will compare one job with multiple jobs, shell scripts with actions, broad defaults with narrow overrides, and direct configuration with abstraction—always asking what state is shared, what failure is isolated, what permission is required, and what evidence remains reviewable.

Design patternsJob boundariesLeast scopeReuseTrade-offs

Learning objectives

  • Choose between one job and multiple jobs based on isolation, parallelism, repeated setup, evidence, and failure boundaries rather than YAML aesthetics.
  • Choose run versus uses based on ownership, portability, dependency trust, versioning, and reviewability.
  • Place permissions, environment, shell, and working-directory configuration at the narrowest maintainable scope.
  • Explain the operational difference between an external action, a local action, a copied template, and a workflow that directly contains logic.
  • Evaluate readability versus abstraction with a decision table and predict concrete runner, token, and evidence consequences.

1. The design question: what should be one schedulable unit?

A job is more than indentation. It is a runner allocation, environment boundary, permission scope, log group, retry unit, and potential parallelism unit. Splitting a workflow into jobs can improve isolation and make dependencies explicit, but every split also means another runner setup and an explicit strategy for moving data that cannot be recreated.

Conversely, putting everything into one giant job makes filesystem sharing easy but couples unrelated tasks to the same machine and failure path. The correct answer follows the delivery model, not a rule such as “one job per command” or “one job per workflow.”

2. One job versus multiple jobs

Question Favor one job when… Favor multiple jobs when…
Filesystem Later steps truly depend on ephemeral files from earlier steps. Each job can recreate inputs or exchange them explicitly.
Isolation The steps belong to one trust/failure unit. Build, test, publish, or deploy need distinct privileges or blast radius.
Parallelism Work is inherently sequential and short. Independent work can run concurrently or on different runner types.
Setup cost Repeated checkout/tool setup would dominate useful work. Independent setup is acceptable for clearer boundaries.
Retry behavior Repeating the whole sequence is safe. Targeted rerun boundaries matter and side effects are isolated.
Permissions All steps legitimately need the same narrow permissions. Only one job needs mutation/deployment privilege.

A useful heuristic is to split when trust, side effects, runner requirements, or failure ownership differ. Do not split only to make the YAML visually shorter.

3. run versus uses: ownership and dependency trade-offs

run is appropriate when the logic is small, repository-specific, and clearer as an explicit script. uses is appropriate when a reviewed reusable action owns a well-defined capability such as checkout or toolchain setup. But reuse introduces an executable dependency, so it carries version, provenance, runtime, and trust requirements.

Prefer transparency before abstraction. Three readable shell commands do not automatically need a custom action. Reuse is valuable when it removes duplicated policy/implementation while keeping inputs, outputs, permissions, and action revision explicit.

For third-party code, full-SHA pinning is an immutable reference but not a substitute for source review. The repository owner, source, release process, requested credentials, network behavior, and update process still matter.

4. External action versus local action

An external action such as actions/checkout is fetched as a dependency according to its reference. A local action referenced as uses: ./.github/actions/example is part of your repository tree. On a hosted runner, that local repository content normally must be available in the workspace first, which means checkout is commonly required before invoking the local action.

Local actions can centralize repeated repository-specific logic, but they are still executable code and should have a clear interface. Chapter 17 will teach custom action implementation in depth; here the design lesson is simply that “local” changes the ownership/versioning boundary, not the need for review.

5. Workflow-level versus job-level versus step-level configuration

Put a setting at workflow scope only when it is genuinely a workflow-wide invariant. Put it at job scope when one job needs a different runner, permission set, environment, or default. Use step scope for exceptional shell/working-directory behavior.

permissions: {}

defaults:
  run:
    shell: bash

jobs:
  verify:
    permissions:
      contents: read
    runs-on: ubuntu-24.04
    defaults:
      run:
        working-directory: ./tools
    steps:
      - name: One step needs repository root
        working-directory: .
        run: pwd

The principle is least scope: broad defaults reduce repetition only when they do not hide meaningful differences. A production workflow should make privilege escalation and target changes conspicuous in review.

6. Stability: job IDs and check names can become external interfaces

Branch protection, dashboards, API consumers, or human runbooks may come to depend on job/check names. Refactoring identifiers is therefore not always cosmetic. Stable IDs and clear display names reduce accidental breakage, especially as workflows become reusable or governed organization-wide.

Keep machine-facing IDs concise and semantic. Keep human-facing names specific enough that a failed check can be understood without opening the YAML. Avoid auto-generated-looking names that make incident evidence ambiguous.

7. Readability versus abstraction

Pattern Strength Risk Use when
Inline run steps Maximum local visibility. Duplication and shell portability drift. Logic is small and repository-specific.
Repository script called by run Testable outside Actions; clear ownership. Still needs tool/runtime discipline. Logic is substantial but belongs to the repository.
Local action Reusable step interface in one repo. Can hide behavior and adds action metadata/runtime concerns. Repeated action-shaped capability deserves an interface.
External action pinned to SHA Reusable maintained capability. External executable dependency/supply-chain risk. Benefit outweighs dependency risk and source/release are trusted.
Reusable workflow Whole-job/platform contract. Interface, permission, nesting, and secret propagation complexity. Cross-repository pipeline policy belongs at workflow boundary; covered later.

8. Worked scenario: build, test, and publish are not one trust boundary

Suppose a future pipeline has three concerns: compile source, run tests, and publish a release package. Compilation and tests need read-only repository access. Publication mutates an external registry and should run only for an authorized release event. A single job would make it easier to leak publication credentials into build steps and harder to rerun safely.

A stronger design is separate read-only build/test jobs plus a tightly gated publication job that depends on verified outputs. Chapter 02 does not implement package publication yet; the point is to recognize the future privilege boundary now. Job structure is how delivery policy becomes visible before credentials are added.

9. Decision checklist

  • State: Does the next unit require the same ephemeral filesystem, or can inputs be recreated/transferred explicitly?
  • Trust: Does one unit execute less-trusted code or need fewer privileges?
  • Runner: Do units need different OS, architecture, hardware, or isolation?
  • Failure: Should a reviewer be able to rerun one unit without repeating side effects?
  • Evidence: Will logs/check names clearly identify the failed capability?
  • Dependency: Is an action/reusable component worth its external code and update surface?
  • Maintenance: Is configuration placed broadly because it is truly invariant, or only to save lines?

10. Production pattern and Chapter 02 boundary

The production pattern is not “more YAML.” It is a graph whose nodes align with trust, runner, side-effect, and recovery boundaries. Start explicit. Extract shared components only after their interface is understood. Grant permissions narrowly. Treat action references as dependencies. Keep stable names where external policy depends on them.

Next lesson

Diagnose structure and execution separately

Lesson 4 introduces structural/schema, job-isolation, shell, key-support, and mutable-action-reference failures without destroying first-failure evidence.

Knowledge check

What is the strongest reason to split publication from read-only build/test work?

When is a repository script often clearer than a custom action?

Why can changing a job name be operationally significant?

Why is a workflow-wide working directory not always a good deduplication?

Does full-SHA pinning eliminate the need to review an external action?

Official references and version notes

Version and compatibility note

Version-sensitive behavior was rechecked against primary GitHub documentation and GitHub-maintained action repositories on 2026-09-09. Executable examples use ubuntu-24.04, actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 (upstream release v7.0.1), and where Python setup is needed actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 (upstream release v7.0.0) with Python 3.13. At verification time both actions declare a Node 24 runtime. Runner images, action releases/runtimes, workflow keys, parser diagnostics, and plan-dependent behavior can change; re-resolve current immutable SHAs before copying these examples into long-lived production workflows. The design exercise intentionally stops before artifacts, reusable workflows, environments, OIDC, releases, or external deployment; those later chapters add the data/credential mechanisms needed to implement the architecture safely.

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
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this address.