CI/CD (GitHub Actions) Fundamentals¶
Anatomy of a workflow¶
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
- run: npm ci
- run: npm test
on declares the events that trigger the workflow — here, every
push to main and every pull request. jobs defines one or more
independent units of work; test is this workflow's only job.
runs-on picks the runner (the VM the job executes in). steps are
the sequential actions within a job — uses runs a reusable, packaged
action (checkout clones the repo, setup-node installs a specific
Node version); run executes a shell command directly.
Jobs run in parallel, in isolated environments, by default¶
Unless a needs: dependency is declared, every job in a workflow
starts simultaneously, each in its own fresh virtual machine
(or container) — there is no shared filesystem, no shared process
state, and no implicit ordering between jobs. This is a deliberate
design, not a limitation to work around by cramming everything into
one job: it's what lets independent checks (lint, unit tests,
integration tests) run concurrently instead of one after another,
directly cutting total pipeline time.
jobs:
build:
runs-on: ubuntu-latest
steps: [...]
deploy:
needs: build
runs-on: ubuntu-latest
steps: [...]
needs: build makes deploy wait for build to succeed first —
this is how a real dependency (don't deploy code that hasn't passed
its build) gets expressed explicitly, rather than relying on jobs
happening to run in file order (they don't).
Steps within a job DO share state — the one exception¶
Steps within the same job run sequentially on the same runner and do share the filesystem (a file created in step 1 is visible in step 2) — the isolation boundary is between jobs, not between steps within one job. This asymmetry is the direct reason artifacts exist as a distinct mechanism — they're specifically for passing files between jobs, a problem that doesn't exist between steps in the same job.
Common pitfall¶
Assuming a later job in the same workflow can see files a previous job created (a build output, a generated config) without explicitly uploading and downloading them via artifacts is one of the most common first-time GitHub Actions mistakes — each job is a genuinely separate machine with an empty filesystem at the start, and nothing from a prior job's workspace is automatically present, regardless of how naturally sequential the jobs feel when reading the YAML top to bottom.