GitHub Actions workflows are written in YAML. The schema is small but expressive — every advanced feature builds on these primitives. This lesson covers every element you will use day-to-day.
The Top-Level Keys
| Key | Purpose |
|---|---|
name | Display name in the Actions UI |
on | Triggers that start the workflow |
permissions | GITHUB_TOKEN scopes for the workflow |
env | Workflow-wide environment variables |
concurrency | Cancel/queue policies for concurrent runs |
jobs | The work to do |
Triggers (on:)
Common event triggers
on:
push:
branches: [main, 'release/**']
paths-ignore: ['**.md']
pull_request:
types: [opened, synchronize, reopened]
schedule:
- cron: '0 6 * * *' # daily at 06:00 UTC
workflow_dispatch: # manual button in the UI
inputs:
environment:
type: choice
options: [staging, production]
default: staging
release:
types: [published]
Repository, organization, and external events
GitHub Actions supports 35+ event types. The most common beyond push/PR are workflow_dispatch (manual trigger with inputs), workflow_call (called by another workflow — the foundation of reusable workflows), and repository_dispatch (triggered by external API calls).
Jobs
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm test
build:
needs: test # only runs if 'test' succeeds
runs-on: ubuntu-latest
steps:
- run: npm run build
deploy:
needs: [test, build]
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- run: ./deploy.sh
Jobs default to parallel execution. Use needs: to declare dependencies and create a directed acyclic graph (DAG) of execution.
Job-level settings
runs-on— runner label (ubuntu-latest, windows-latest, macos-latest, or self-hosted labels)timeout-minutes— defaults to 360; useful to cap runaway buildsstrategy.matrix— fan-out variant builds (covered in lesson 6)environment— link a job to a deployment environment for approvals/secretsdefaults— set default shell, working-directorycontainer— run all steps inside a Docker imageservices— sidecar containers (e.g., Postgres for tests)
Steps
Two step types:
steps:
- name: Run a shell command
run: |
echo "Hello"
npm ci
- name: Use a pre-built Action
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
Inside one job, steps share the same runner filesystem. They do not share environment-variable mutations across steps unless you write them to $GITHUB_ENV:
- name: Compute and export a variable
run: echo "SHA_SHORT=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
- name: Use it in the next step
run: echo "Short SHA is $SHA_SHORT"
Expressions and Contexts
Anywhere you see ${{ ... }}, you're inside an expression. Expressions resolve against contexts:
| Context | Contains |
|---|---|
github | Event payload, repo, ref, actor, sha, run_id |
env | Environment variables |
secrets | Repository, environment, or org secrets |
vars | Non-secret configuration variables |
inputs | workflow_dispatch / workflow_call inputs |
needs | Outputs from prior jobs |
matrix | Current matrix variant values |
steps | Outputs from prior steps (by step id) |
runner | Runner OS, arch, temp paths |
Conditionals
jobs:
deploy:
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- if: ${{ !cancelled() }}
run: ./deploy.sh
Useful functions inside if: include success(), failure(), always(), cancelled(), contains(), startsWith(), endsWith(), fromJson().
Outputs Between Steps and Jobs
jobs:
version:
runs-on: ubuntu-latest
outputs:
tag: ${{ steps.compute.outputs.tag }}
steps:
- id: compute
run: echo "tag=v$(date +%s)" >> $GITHUB_OUTPUT
release:
needs: version
runs-on: ubuntu-latest
steps:
- run: echo "Releasing ${{ needs.version.outputs.tag }}"
Concurrency
concurrency:
group: deploy-${{ github.ref }}
cancel-in-progress: true
This ensures only one deployment per branch runs at a time — newer commits cancel older in-flight runs. Essential for any deploy workflow.
Putting It All Together
You now have the building blocks: triggers fire workflows, workflows contain jobs, jobs contain steps, expressions resolve dynamic values, and conditionals control execution. The next four lessons go deep on the components that make these workflows actually useful: actions, runners, secrets, and reusability.