Every GitHub Actions pipeline is a YAML file. In this lesson you will read a complete annotated workflow and learn each part: events, jobs, steps, runners, expressions, conditions, outputs, concurrency and permissions.
๐ What You’ll Learn: the building blocks of a workflow, a complete annotated example, events and filters, job keys like needs, if and timeout, steps with uses and run, expressions and contexts, status functions, passing outputs between steps and jobs, and runners and cost.
๐งฉ Anatomy of a Workflow
A workflow is a YAML file in .github/workflows/ that GitHub Actions runs when an event happens. The building blocks:
| Part | Meaning |
|---|---|
| Workflow | One YAML file; a repository can have many |
Event (on) | What starts it: push, pull request, schedule, manual run… |
| Job | A set of steps that runs on one runner (virtual machine); jobs run in parallel by default |
| Step | One command (run) or one reusable action (uses) |
| Runner | The machine: GitHub-hosted (Ubuntu, Windows, macOS) or self-hosted |
| Action | A reusable unit from the Marketplace or your own repository |
If you are completely new, read GitHub Actions Fundamentals for Beginners first.
๐ A Complete Annotated Workflow
name: CI # shown in the Actions tab
run-name: CI for ${{ github.ref_name }} by @${{ github.actor }}
on:
push:
branches: [ main ]
paths-ignore: [ '**.md' ] # skip runs for documentation-only changes
pull_request:
branches: [ main ]
schedule:
- cron: '30 20 * * *' # every day at 20:30 UTC
workflow_dispatch: # manual "Run workflow" button
inputs:
browser:
description: 'Browser project to run'
type: choice
options: [ chromium, firefox, webkit ]
default: chromium
permissions:
contents: read # least privilege for GITHUB_TOKEN
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true # cancel older runs of the same branch
env:
NODE_VERSION: 22
jobs:
lint:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- run: npm ci
- run: npm run lint
test:
needs: lint # runs after lint succeeds
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- run: npm ci
- run: npx playwright install --with-deps
- name: Run tests
run: npx playwright test --project=${{ inputs.browser || 'chromium' }}
- name: Upload report
if: ${{ !cancelled() }}
uses: actions/upload-artifact@v4
with:
name: playwright-report
path: playwright-report/
โก Events (on)
| Event | Fires when | Common filters |
|---|---|---|
push | Commits are pushed | branches, tags, paths, paths-ignore |
pull_request | A PR is opened, updated or reopened | branches (the target branch), types |
schedule | On a cron timetable (UTC) | cron expression |
workflow_dispatch | Someone clicks Run workflow (or uses the API or gh workflow run) | inputs |
workflow_call | Another workflow calls this one | inputs, secrets (reusable workflows) |
release | A release is published | types: [published] |
workflow_run | Another workflow finishes | workflows, types |
๐ก Tip: Use paths / paths-ignore so a README change does not trigger a 20-minute test run.
๐๏ธ Jobs
| Key | Purpose |
|---|---|
runs-on | Runner: ubuntu-latest, windows-latest, macos-latest, or labels of self-hosted runners |
needs | Dependencies: needs: [lint, build] |
if | Run the job only when a condition is true |
timeout-minutes | Stop runaway jobs (default is 360) |
strategy.matrix | Run the job for several combinations (matrix) |
environment | Deploy to a protected environment (environments) |
outputs | Values passed to later jobs |
container / services | Run in a Docker image, or start databases alongside the job |
๐ช Steps
steps:
- name: Check out code
uses: actions/checkout@v4 # an action, with inputs under "with"
with:
fetch-depth: 0
- name: Install
run: npm ci # a shell command
- name: Multi-line script
working-directory: ./tests
shell: bash
run: |
echo "Running on ${{ runner.os }}"
ls -la
- name: Optional step
continue-on-error: true
run: npm run audit
๐งฎ Expressions and Contexts
Expressions are written as ${{ ... }} and can read contexts:
| Context | Examples |
|---|---|
github | github.ref_name, github.sha, github.event_name, github.actor |
env / vars / secrets | Environment variables, configuration variables, secrets |
inputs | Inputs from workflow_dispatch or workflow_call |
matrix | The current matrix values |
steps / needs | Outputs of earlier steps or jobs |
runner | runner.os, runner.temp |
Status functions for if: success() (default), failure(), always(), cancelled(). Other functions: contains(), startsWith(), format(), hashFiles(), toJSON().
- name: Notify on failure
if: ${{ failure() && github.ref == 'refs/heads/main' }}
run: echo "Main is broken!"
๐ Passing Data Between Steps and Jobs
jobs:
version:
runs-on: ubuntu-latest
outputs:
app_version: ${{ steps.read.outputs.version }}
steps:
- uses: actions/checkout@v4
- id: read
run: echo "version=$(node -p "require('./package.json').version")" >> "$GITHUB_OUTPUT"
report:
needs: version
runs-on: ubuntu-latest
steps:
- run: echo "Testing version ${{ needs.version.outputs.app_version }}"
Other special files: $GITHUB_ENV (set environment variables for later steps) and $GITHUB_STEP_SUMMARY (Markdown shown on the run’s summary page).
๐ฐ Runners and Cost
GitHub-hosted runners are free for public repositories. Private repositories get a monthly allowance of free minutes depending on the plan; Windows and macOS minutes count more than Linux. Use Linux runners, timeout-minutes, concurrency and paths filters to keep usage low. Self-hosted runners run on your own machines for special hardware or network access.
๐ Assignment
- In
git-practice, create.github/workflows/ci.ymlwithpush,pull_requestandworkflow_dispatchtriggers. - Add two jobs,
lintandtest(they can justecho), wheretestneedslint. - Add a
workflow_dispatchinput and print it in a step. - Add
concurrencywithcancel-in-progressand push twice quickly to see the first run cancelled. - Pass a value from one job to another with outputs, and write a line to
$GITHUB_STEP_SUMMARY. - Add a nightly
scheduleand apaths-ignorefor Markdown files.
โ FAQ
๐ค Where do GitHub Actions workflow files go?
In the .github/workflows/ folder of the repository, as .yml or .yaml files.
๐ค What is the difference between a job and a step?
A job runs on its own runner and contains steps. Steps run in order inside the job; jobs run in parallel unless connected with needs.
๐ค How do I run a workflow manually?
Add the workflow_dispatch trigger, then click Run workflow on the Actions tab or use gh workflow run.
๐ค How do I run a step only when a previous step fails?
Use if: ${{ failure() }} on the step.
๐ค What time zone does schedule cron use?
UTC. Convert your local time, for example 02:00 IST is 20:30 UTC on the previous day.
๐ฏ Next Steps
- ๐งช GitHub Actions Example Pipelines
- ๐ Variables, Secrets and Environments
- ๐ Git and GitHub Tutorial: full course
๐ฌ Learn Git and GitHub step by step: watch the series on YouTube!
Subscribe to SqaToolsByDY • Practice on sqatools.in
1 thought on “GitHub Actions Workflow Syntax: Events, Jobs, Steps, Runners and Expressions”