CI/CD · AUTOMATION

GitHub Actions Cheat Sheet

Mental model: an event triggers a workflow (YAML in .github/workflows/); a workflow runs one or more jobs in parallel (or sequenced via needs) on runners; each job runs an ordered list of steps, which are either shell commands or reusable actions.

triggers / on: jobs / strategy steps / actions contexts / expressions security / danger most common
Sources: docs.github.com/actions (workflow syntax, contexts, expressions, security, OIDC, reusable workflows) · GitHub Changelog · actions/checkout, actions/cache, actions/upload-artifact release notes

Lifecycle of a Workflow Run

Event → workflow file match → job scheduling → runner execution → artifacts / status

Event push / PR / cron / dispatch Workflow YAML .github/workflows/ *.yml matched by on: job: build runs-on: ubuntu-latest job: test matrix strategy job: deploy needs: [build, test] needs Runner fresh VM / container GitHub-hosted or self-hosted uses: checkout run: build/test uses: upload-artifact Result check status artifacts / logs GITHUB_STEP_SUMMARY contexts (github · env · vars · secrets · needs · matrix · steps) flow through every stage →
01 · CORE STRUCTURE
1

Minimal Workflow Skeleton

Every workflow file needs these top-level keys

  • name: CIshown in the Actions tab
  • on: [push, pull_request]event(s) that trigger the run
  • jobs:map of job_id → job config
  •   runs-on: ubuntu-latestrequired per job
  •   steps:ordered list, run top to bottom
2

Where Files Live

Repository layout conventions

  • .github/workflows/*.ymlworkflow definitions (must be on default branch for some events)
  • action.yml / action.yamlmetadata file for a custom action
  • .github/CODEOWNERS, issue/PR templatesnot Actions, but same directory family
3

Triggers — Common Events

on: keys, single event, list, or map with filters

  • push:commits pushed; filter with branches, tags, paths
  • pull_request:PR opened/sync/etc; filter with types
  • workflow_dispatch:manual run button; supports typed inputs
  • schedule: cron: '0 3 * * *'UTC cron; min interval ~5 min, may be delayed under load
  • release: types: [published]release lifecycle events
  • workflow_call:makes this workflow reusable by others
  • repository_dispatch:custom event fired via REST API
  • pull_request_target: dangerruns with base-repo secrets against fork code — never checkout + run untrusted PR code here
4

workflow_dispatch Inputs

Typed parameters for manual runs

  • type: choice / string / boolean / environmentsupported input types
  • required: true, default: 'x'standard input fields
  • ${{ github.event.inputs.name }}or the shorter ${{ inputs.name }}
02 · JOBS & RUNNERS
5

Job-Level Keys

Configured per entry under jobs:

  • runs-on:required — runner label or expression
  • needs: [job_a, job_b]run only after listed jobs succeed; enables sequencing
  • if:skip the whole job unless condition is true
  • timeout-minutes: 15default is 360; always set explicitly
  • continue-on-error: truejob/step failure won't fail the run
  • environment: productionties job to protection rules & env secrets
  • outputs:map job_output → ${{ steps.id.outputs.x }}
  • container: node:20run job steps inside a Docker container
  • services:sidecar containers (e.g. postgres, redis) for the job
6

Matrix Strategy

Fan a job out across a combination grid

  • strategy: matrix:define axes, e.g. os: [ubuntu-latest, macos-latest]
  • include: / exclude:add one-off combos or remove specific ones
  • fail-fast: falsedefault true cancels the whole matrix on first failure
  • max-parallel: 2throttle concurrent matrix jobs
  • ${{ matrix.os }}, ${{ matrix.node }}reference axis values anywhere in the job
7

Runners

Where the job actually executes

  • ubuntu-latest, windows-latest, macos-latestGitHub-hosted, fresh VM per job
  • ubuntu-24.04-armArm-based hosted runner
  • ubuntu-latest-16-coreslarger hosted runner (paid, org/enterprise)
  • [self-hosted, linux, gpu]label set routes to self-hosted fleet
  • runner.os, runner.arch, runner.temprunner context, available once job is scheduled
8

Concurrency

Prevent overlapping runs

  • concurrency: group: ${{ github.ref }}only one run per group at a time
  • cancel-in-progress: trueauto-cancel superseded runs (great for PR pushes)
  • queue: single | maxhow many pending runs may wait in the group
  • concurrency can be workflow- or job-leveljob-level = only that job is serialized
03 · STEPS & ACTIONS
9

Step-Level Keys

Each list item under steps:

  • uses: owner/repo@refrun a published or local action
  • run: echo hishell command; multi-line via |
  • with:input parameters passed to the action
  • id: buildreference this step's outputs later
  • env:environment variables for this step only
  • if:skip this one step conditionally
  • shell: bashbash, pwsh, python, sh, cmd, powershell
  • working-directory: ./appcwd for the run: command
  • continue-on-error: truelet this step fail without failing the job
10

Essential Marketplace Actions

The 90%-of-workflows toolkit

  • actions/checkout@v4clone the repo; needed before most git-aware steps
  • actions/setup-node@v4, setup-python@v5, setup-java@v4install a language runtime, with built-in caching option
  • actions/cache@v4restore/save dependency cache by key
  • actions/upload-artifact@v4 / download-artifact@v4persist files between jobs / for later download
  • actions/github-script@v7run JS with an authenticated Octokit client inline
  • actions/create-release, softprops/action-gh-releasepublish GitHub Releases
  • docker/build-push-action@v6build & push container images
11

Checkout Options

Common with: tweaks for actions/checkout

  • fetch-depth: 0full history (default is depth 1, shallow)
  • ref: ${{ github.event.pull_request.head.sha }}check out a specific ref/SHA
  • persist-credentials: false securityavoid leaving the token usable by later steps/scripts
  • submodules: truealso check out submodules
12

Caching (actions/cache)

Speed up installs; best-effort, not guaranteed

  • key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}exact-match key, usually hash of a lockfile
  • restore-keys:fallback prefixes for a partial cache hit
  • path:directory/directories to cache
  • setup-node: cache: 'npm'many setup-* actions wrap cache automatically
13

Artifacts

Durable output files (cache ≠ artifact)

  • upload-artifact: name / pathstore build output, test reports, binaries
  • retention-days: 7default 90; shorten to save storage
  • if-no-files-found: warn|error|ignorebehavior when the path matches nothing
  • download-artifact: pattern:pull one or many artifacts into a later job
14

Workflow Commands (from a step)

Talk back to the runner via files/echo

  • echo "x=1" >> $GITHUB_OUTPUTset a step output (replaces deprecated ::set-output::)
  • echo "VAR=1" >> $GITHUB_ENVexport an env var to subsequent steps
  • echo "..." >> $GITHUB_STEP_SUMMARYmarkdown shown on the run summary page
  • echo "::error file=a.js::msg"annotate; also ::warning::, ::notice::
  • echo "::group::name" ... "::endgroup::"collapsible log sections
  • echo "::add-mask::$VALUE" securityredact a runtime-computed secret in logs
04 · CONTEXTS & EXPRESSIONS
15

Core Contexts

Objects available inside ${{ }}

  • github.*event payload, sha, ref, actor, repository, token
  • env.*, vars.*workflow/job env vars & configuration variables
  • secrets.*encrypted secrets; not printed to logs (masked)
  • needs.<job>.outputs.*, needs.<job>.resultread another job's outputs/status
  • matrix.*current combination in a matrix job
  • steps.<id>.outputs.*, steps.<id>.outcomeread a prior step's outputs/status
  • job.status, runner.*, inputs.*job status; runner info; workflow_call/dispatch inputs
16

Expression Functions

Built-ins usable inside ${{ }}

  • contains(), startsWith(), endsWith()string/array matching
  • format('{0}-{1}', a, b)string templating
  • join(array, sep)array to string
  • toJSON(), fromJSON()debug a context / parse a JSON string or matrix
  • hashFiles('**/lock')deterministic hash, common for cache keys
17

Status-Check Functions (for if:)

Control step/job execution around failures

  • success()default implicit condition — all previous steps passed
  • failure()true if any previous step failed
  • always()runs even if cancelled — risky for checkout-dependent steps
  • !cancelled()preferred over always() to still skip on cancellation
  • cancelled()true only if the run was cancelled
18

Secrets & Variables

Config vs. sensitive values

  • secrets.GITHUB_TOKENauto-generated per run; scope set via permissions:
  • secrets.MY_TOKENrepo / environment / org-level secret
  • vars.MY_VARnon-sensitive configuration variable, same scoping tiers
  • secrets: not usable in if: directlycopy to an env var first, then check the env var
05 · SECURITY, ENVIRONMENTS & REUSE
19

Permissions (GITHUB_TOKEN)

Least-privilege scoping

  • permissions: read-all is not the defaultnew repos default to read-only contents since 2023
  • permissions: contents: writegrant only the scopes a job actually needs
  • permissions: {}explicitly deny everything as a safe baseline
  • id-token: writerequired to mint an OIDC token for cloud auth
20

Environments & Protection Rules

Gate deploys behind approvals

  • jobs.<id>.environment: productionties the job to an environment's rules & secrets
  • required reviewersmanual approval before the job proceeds
  • wait timermandatory delay before deployment
  • deployment branch/tag policyrestrict which refs may deploy
21

Reusable Workflows

workflow_call vs plain workflow_dispatch

  • on: workflow_call: inputs / secrets / outputsdefines the called workflow's contract
  • jobs.<id>.uses: org/repo/.github/…/x.yml@maininvoke it from a caller workflow
  • with: / secrets:pass inputs and secrets down to the callee
  • secrets: inheritpass all caller secrets through (convenient, less explicit)
22

Custom Action Types

action.ymlruns.using

  • using: compositewraps existing steps; simplest, runs on the caller's runner
  • using: node20JavaScript/TypeScript action; fast, cross-platform
  • using: dockerpackages a Dockerfile; heavier but full OS control (Linux runners only)
23

Security Best Practices

Reduce supply-chain & secret-leak risk

  • uses: owner/action@<full-SHA>pin third-party actions by commit SHA, not a mutable tag
  • Never checkout PR code in pull_request_targetthat job runs with base-repo secrets — classic fork attack vector
  • id-token: write + OIDCprefer short-lived federated tokens over long-lived cloud secrets
  • Treat event payload fields as untrusted inpute.g. issue titles/PR titles injected into run: can execute arbitrary shell
24

Common Gotchas

Frequent points of confusion

  • secrets are unavailable to workflows triggered from forksby design, for pull_request (not _target)
  • 360-minute default job timeoutset timeout-minutes explicitly to fail fast & control cost
  • Cache key with no restore-keys fallbacka pure lockfile-hash key misses on every dependency bump
  • Multiline YAML in run: needs |and watch indentation — YAML is whitespace-sensitive

OIDC — Secret-less Cloud Authentication

Trade long-lived cloud keys for a short-lived token minted per run

Workflow job permissions: id-token: write requests GitHub OIDC provider mints short-lived signed JWT presents Cloud IdP AWS STS / Azure AD / GCP Workload Identity validates trust Temp credentials minutes-scoped, repo/branch-scoped no cloud secret ever stored in GitHub — trust configured once on the cloud side by repo/ref condition
06 · VISUAL COMPARISONS

Matrix Expansion

matrix: os:[ubuntu,windows] × node:[18,20] ubuntu+18 ubuntu+20 windows+18 windows+20 4 parallel jobs from one job definition — each gets its own matrix.os / matrix.node values. fail-fast: true (default) cancels the rest of the grid on the first failure.

Reusable Workflow Call

Caller workflow jobs.x.uses: ...yml@main with / secrets Called workflow on: workflow_call outputs Centralizes shared CI logic (lint/test/build) across many repos — version-pinned like any action. workflow_call = called by other workflows. workflow_dispatch = triggered manually by a person.

Custom Action Types

Composite Wraps steps you'd otherwise repeat. Fastest to build, runs inline. JavaScript / Node20 Full logic in JS/TS, uses @actions/core & @actions/github SDKs. Docker container Any language, full OS deps — slower cold start, Linux-only. Pick composite first; reach for JS/Docker for real logic or portability.

Event Danger Zones

pull_request Runs with fork's code, read-only token, no access to repo secrets. Safe default. pull_request_target Runs against the base repo with full secret access — if it also checks out and executes the fork's code, secrets leak. Rule of thumb: never combine _target + fork checkout + run.

Worth Memorizing

cache ≠ artifactcache = best-effort speed-up, may be evicted; artifact = guaranteed durable output
workflow_dispatch ≠ repository_dispatchmanual UI/API trigger for this repo vs. custom event fired from anywhere via API
needs ≠ ifneeds sequences jobs; if conditionally skips regardless of order
secrets ≠ varssame scoping tiers, but secrets are encrypted & masked, vars are plain config
always() ≠ !cancelled()always() can hang a cancelled run; !cancelled() is the safer idiom
pull_request ≠ pull_request_targetthe latter runs with base-repo secrets against fork content — handle with care
on.workflow_call ≠ on.workflow_dispatchreusable-by-other-workflows vs. manually-triggered-by-a-human
GITHUB_TOKEN defaultread-only permissions on repos created since 2023 — opt in to write scopes explicitly
::set-output:: is deprecateduse $GITHUB_OUTPUT / $GITHUB_ENV environment files instead
Pin third-party actions by SHAa mutable tag like @v4 can be moved by the action's maintainer (or an attacker)