Lesson 01 of 9 · Level 1 — Foundations
Workflows, events and your first pipeline
Your first GitHub Actions pipeline: where workflows live, which events trigger them, how jobs and steps are written, how to use ready-made actions, secrets and variables, job dependencies, and the permissions of the built-in token.
Where workflows live
A workflow is a YAML file in .github/workflows/. It says when to run (on:), and what to run (jobs:), each job on a runner (a VM or container) with a sequence of steps.
A workflow is a to-do list pinned to a door. "When someone knocks (an event), do these jobs." Each job is handed to a helper (a runner), who works through the steps in order and reports back.
A first pipeline
# .github/workflows/ci.yml
name: ci
on:
push:
branches: [main]
pull_request:
workflow_dispatch:
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install -r requirements.txt
- run: pytest --junitxml=reports/unit.xml
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: pipx run ruff check .
build:
needs: [test, lint] # only if both succeed
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: echo "build and publish here (lesson 04)"
testandlintrun in parallel;buildwaits for both (needs).if:limitsbuildto pushes onmain; pull requests only test and lint.actions/checkoutfetches the repository into the runner; nothing is there otherwise.
Events you'll use most
| Event | Runs when | Typical use |
|---|---|---|
push |
Commits pushed (filter by branches, tags, paths) | Build main, release tags |
pull_request |
PR opened/updated | Tests and checks before merging |
workflow_dispatch |
Someone clicks "Run workflow" (with inputs) | Manual jobs, one-off operations |
schedule |
Cron (UTC) | Nightly scans, drift checks |
release |
A release is published | Publish packages |
workflow_call |
Another workflow calls this one | Reusable workflows (lesson 03) |
paths: filters let a monorepo run only the workflows affected by a change.
Secrets and variables
- Secrets (repository, environment or organisation level) are encrypted, masked in logs, and read as
${{ secrets.NAME }}. - Variables (
${{ vars.NAME }}) are for non-secret configuration. - Pass secrets to steps through
env:, not by printing them; prefer OIDC over stored cloud keys altogether (lesson 06).
- run: ./deploy-notify.sh
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
CHANNEL: ${{ vars.RELEASE_CHANNEL }}
The built-in token and permissions
Every run gets a short-lived GITHUB_TOKEN. Set its permissions explicitly, at the top of the workflow or per job, to exactly what's needed (contents: read, plus packages: write to push images, id-token: write for OIDC, pull-requests: write to comment). Organisation defaults can also be set to read-only.
Try it: first workflow
- Add the workflow above to a repository with a tiny Python project (or change the steps to your language).
- Push to a branch and open a pull request:
testandlintrun,buildis skipped. - Merge:
buildruns after the other two. - Use
gh run list,gh run watchandgh run view --log-failedafter breaking a test on purpose. - Make the
testcheck required in branch protection.
Recap
- Workflows live in
.github/workflows/;on:decides when,jobs:what, each job on a runner. - Steps either use an action or run shell; jobs run in parallel unless linked with needs.
- Secrets and vars for configuration; secrets are withheld from fork PRs.
- Set permissions for
GITHUB_TOKENexplicitly.
This site is a public version of my personal engineering knowledge hub. It intentionally excludes confidential company information and internal operational details.