logo
NotesNotesResumeResume
© 2026 Kiet Nguyen
← All notes
Bold 2D pop art with the word YAML and nested flat shapes

June 13, 2026·8 min read

Understand YAML under 10 minutes

YAML · CICD · CI · CT · Toolchain · DevOps

A dense, honest pass over YAML as the language of pipelines: structure, types, gotchas, and the patterns that show up in GitHub Actions, GitLab CI, and Jenkins-adjacent configs, so continuous testing YAML stops looking like magic.

What YAML is (and is not)

YAML (YAML Ain’t Markup Language) is a data serialization format: nested maps, lists, and scalars that humans can edit and tools can parse into objects.

It isIt is not
The usual surface for CI “workflow as code”A programming language (no real loops/functions, only what the runner implements)
Indentation-sensitive structureFree-form prose (one wrong space can change meaning)
A tree of keys → valuesProof that tests ran (only the steps and exit codes prove that)

Mental model: every workflow file is a tree. Indentation is the tree. Colons introduce keys. Dashes introduce list items. Quotes protect strings that would otherwise look like booleans or numbers.

workflow (map)
├── name: string
├── on: map | list | string
└── jobs: map
    └── build: map
        ├── runs-on: string
        └── steps: list
            ├── map (uses: …)
            └── map (run: …)

If you can draw that tree for a file, you can extend it.

Ten minutes of syntax that actually matters

1. Maps (objects)

Key, colon, value. Nested maps indent two spaces by convention (some tools accept more; never mix tabs).

job:
  name: host-tests
  timeout-minutes: 15

2. Lists (sequences)

Each item starts with - at the same indent level.

steps:
  - name: Checkout
    uses: actions/checkout@v4
  - name: Run suite
    run: pytest -q

A list of plain strings:

tags:
  - Validation
  - CI
  - CT

3. Scalars: strings, numbers, booleans, null

count: 3
enabled: true
note: plain string
empty: null

Trap: unquoted yes, no, on, off, true, false are often booleans. Branch names and job ids that look like words should be quoted when in doubt:

# Safer when the value is a label, not a flag
ref: "on"
env_name: "no"

4. Multiline strings: | vs >

FormMeaningUse
|Literal block: keep newlinesShell scripts in run:
>Folded block: newlines → spaces (mostly)Long prose descriptions
|- / >-Strip final newlineWhen trailing newline must not exist
run: |
  set -euo pipefail
  cmake --build build
  ctest --output-on-failure

For CI, prefer | for anything a shell will execute. Folded > is easy to misread when debugging.

5. Comments

# Full-line comment
timeout-minutes: 15  # end-of-line comment

Comments are for humans. They do not run. Do not put secrets in comments “temporarily.”

6. Anchors and aliases (optional but powerful)

Reuse a subtree without copy-paste:

x-default-runner: &default_runner
  runs-on: ubuntu-latest

jobs:
  unit:
    <<: *default_runner
    steps:
      - run: echo unit
  integration:
    <<: *default_runner
    steps:
      - run: echo integration

Not every CI product supports every merge key the same way. When in doubt, duplicate the small block, clarity beats cleverness in a green suite.

7. What YAML deliberately does not do

  • No native ${{ }} logic — that is GitHub Actions expression syntax inside strings the platform evaluates.
  • No native include: semantics — GitLab and others define include/extends; YAML only holds the keys.
  • No guarantee two files that “look similar” mean the same thing on Jenkins vs GHA vs Azure DevOps.

Rule: learn YAML structure once; learn product schema per platform.

The CI-shaped tree (same bones, different labels)

Most “advanced” workflows are still: when → which jobs → which steps → what artifacts/secrets.

IdeaGitHub ActionsGitLab CIJenkins (declarative / JCasC-ish)
Triggeron:rules: / branch pipelinestriggers { } / multibranch
Job graphjobs: + needs:stages: + job namesstages { } / parallel
Runnerruns-on:tags: / runnersagent { }
Stepssteps: (run / uses)script:steps { sh '…' }
Envenv:variables:environment { } / credentials
Matrixstrategy.matrixparallel: matrixmatrix / axis plugins
Artifactsactions/upload-artifactartifacts:archiveArtifacts

If you can answer those seven rows for a file, you can read it.

Minimal continuous testing shape (GitHub Actions flavor)

Honest portfolio pattern: build or install tools → run tests → fail the job on non-zero → keep a report when you have one.

name: ct-host

on:
  push:
    branches: [main]
  pull_request:

jobs:
  test:
    runs-on: ubuntu-latest
    timeout-minutes: 20
    steps:
      - uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.12"

      - name: Install
        run: |
          python -m pip install -U pip
          pip install -r requirements.txt

      - name: Run host suite
        run: |
          set -euo pipefail
          pytest --junitxml=reports/junit.xml

      - name: Upload junit
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: junit
          path: reports/junit.xml

Patterns that unlock “advanced” workflows

You do not need more YAML primitives. You need these composition patterns on top of the tree.

A. Multiple jobs and ordering

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: make firmware
      - uses: actions/upload-artifact@v4
        with:
          name: image
          path: build/app.bin

  test:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/download-artifact@v4
        with:
          name: image
      - run: ./scripts/smoke_host.sh

needs: is the dependency edge. Advanced DAGs are still jobs + needs (or stages).

B. Matrix builds (one job, many axes)

strategy:
  fail-fast: false
  matrix:
    gcc: ["12", "13"]
    build_type: [Debug, Release]

Use fail-fast: false when you want full evidence across the matrix instead of stopping at the first red cell, often better for validation-style reports.

C. Environment and secrets (structure only)

env:
  CMAKE_BUILD_TYPE: Release

steps:
  - name: Deploy report (example)
    env:
      TOKEN: ${{ secrets.REPORT_TOKEN }}  # platform expression; value not in Git
    run: ./scripts/publish_report.sh

YAML holds the name of the secret slot. The value must never be committed. That is process, not syntax.

D. Conditional steps

- name: Only on main
  if: github.ref == 'refs/heads/main'
  run: ./scripts/publish.sh

Condition syntax is product-specific. Structurally it is still “optional node in the step list.”

E. Reusable workflows / templates (platform features)

Platform ideaWhat you are really doing
workflow_call / reusable workflowCalling another YAML tree with inputs
GitLab include: / extends:Merging maps from other files
Jenkins shared libraryCode reuse outside pure YAML

Learn the host’s include model after the base tree is comfortable.

F. Caching and services (still just keys)

services:
  redis:
    image: redis:7
    ports:
      - 6379:6379

A service block is a nested map under the job. Advanced ops are new keys, same indentation discipline.

Gotchas that burn CI time

GotchaSymptomHabit
Tab charactersParser errors or “looks fine locally”Editor: spaces only; show invisibles
Indent off by oneKey becomes a sibling instead of a childCollapse/expand in an editor that shows structure
Unquoted booleanson: true when you meant a string branch nameQuote ambiguous scalars
Colon in unquoted stringParse errorQuote: "Board: lab-01"
CRLF vs LFRare weirdness on self-hosted Windows runnersPrefer LF in workflow files
Copy-pasted “works on my machine” secretsToken rotation dramaSecret store only
Green without testsPipeline “passes” after compile onlyMake CT an explicit step with non-zero on fail
Assuming YAML = CIBeautiful file, never runsConfirm triggers and branch protection

Quick validate without pushing

  • GitHub: paste into the workflow editor UI, or use actionlint when installed.
  • yamllint: structure and style (does not know GHA schema).
  • GitLab: CI Lint in the project UI.

Schema validation ≠ “tests are good.” It only means the tree is well-formed for that product.

A reading drill (use on any workflow)

Open a real .github/workflows/*.yml or .gitlab-ci.yml and answer out loud:

  1. When does this run? (on / rules / triggers)
  2. How many jobs, and what depends on what?
  3. Where is build vs test (CT)?
  4. What is the fail condition (which command’s exit code)?
  5. What artifacts or logs survive a red run?
  6. Where would a secret be referenced—and is any value in Git?
  7. Is any path lab-only or self-hosted (labels/tags)?

If you can answer those seven, you have enough foundation to modify the file instead of treating it as magic.

From foundations to “any advanced” workflow

Advanced CI is usually one of these stacked on the same YAML tree:

GoalYou will add…
Faster feedbacksplit jobs, caching, path filters
Broader evidencematrix of compilers, boards, Python versions
Safer releasesenvironment gates, manual approval keys (product feature)
Embedded / labself-hosted runners, hardware labels, longer timeouts
Compliance hygienerequired checks, signed commits policy (org), static-analysis job
Reusereusable workflows, includes, shared templates

None of that invents a new data format. It invents policy on top of maps and lists.

What this note deliberately skips

  • Full GitHub Actions expression language reference
  • Groovy / Jenkinsfile as a primary language (different surface; same pipeline ideas)
  • Kubernetes manifests (also YAML; different schema)
  • Claiming a production multi-org fleet

When you need depth, open one official schema doc for your host and map every new key back to: trigger, job, step, env, artifact.

Closing

YAML for CI is a tree with rules. Indentation is structure. Lists are steps and matrices. Strings that look like booleans need quotes. Multiline | is how honest shell blocks enter the file. Platforms then hang when, where, and with which secrets on that tree.

Ten minutes of structure is enough to stop being afraid of workflows. The rest of continuous testing is still the hard part: oracles, exit codes, and evidence you would trust in a review.

Back to notes

Was this page helpful?