- Trigger – when should it run?
- Job – one unit of work (runs on a clean VM).
- Step – a single command inside a job.
- Artifact – pass files between jobs (optional).
Every workflow file is just:
name: whatever
on: { trigger }
jobs:
job-id:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4 # always start with this
- run: any-shell-command
- uses: any-pre-made-action@v1
Exercise 1 – “Hello World”
Trigger: every push
Job: 1 step that prints text.
Key point: the VM already has Ubuntu + bash; we only echoed.
Exercise 2 – Run Jest tests
Trigger: push / pull-request to main
Job: 4 steps
- checkout (download your repo)
- setup-node (install Node 22 + cache node_modules)
- npm ci (clean install)
- npm test (run Jest)
Key point:setup-node
withcache: npm
saves ~30 s next run.
Exercise 3-A – Matrix
Same as Exercise 2, but we added:
strategy:
matrix:
node-version: [21, 22]
GitHub creates two identical jobs, one per value, and runs them in parallel.
You can matrix anything: OS, Python version, browser, etc.
Exercise 3-B – Pipeline (sequential + parallel)
We split work into four jobs and used needs:
to enforce order:
lint → test-matrix → build → deploy
↑ (parallel) ↑
Only lint runs first; the two test jobs run side-by-side; build waits for both; deploy waits for build.
We also used actions/upload-artifact
and download-artifact
to pass the dist/
folder between jobs.
A. Run tests on every PR (Node)
name: CI
on:
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm test
B. Matrix across Node versions
strategy:
matrix:
node: [18, 20, 22]
C. Pass files between jobs
# job 1
- uses: actions/upload-artifact@v4
with:
name: myfiles
path: dist/
# job 2
- uses: actions/download-artifact@v4
with:
name: myfiles
path: dist/
- Missing script – npm run lint → add the script in package.json.
- Linter picky – either fix the code or disable the rule; for labs, disabling is fine.
- YAML indentation – 2 spaces, never tabs.
- Forgot checkout – every job starts in an empty VM; always
uses: actions/checkout@v4
first.
need a Linux command? → run: <bash>
need Node? → uses: actions/setup-node@v4
need Python? → uses: actions/setup-python@v5
need to save files? → uses: actions/upload-artifact@v4
need to read them later? → uses: actions/download-artifact@v4
need secrets (API keys)? → ${{ secrets.YOUR_SECRET }} (add in repo Settings → Secrets)
That’s it.