GitHub Actions: CI/CD Workflows From First Run to Secure Deployments · GitHub Actions Workflows: Triggers, CI and Matrices
A Real CI Workflow: Checkout, Setup, Cache and Test
Build a continuous integration workflow for a Node.js project with dependency caching, a lint step and a timeout.
What you'll build
A CI workflow for a Node.js application: check out, install a Node.js version, restore cached dependencies, install, lint, build and test. The same structure applies to any language.
The idea
Most CI workflows have the same shape:
- Check out the code (
actions/checkout). - Set up the toolchain (
actions/setup-node,setup-python,setup-java,setup-go...). - Restore a cache so dependencies aren't downloaded each time. The
setup-*actions can do this for you withcache: npm(orpip,maven,gradle). - Install, lint, build, test.
Use npm ci rather than npm install in CI; it installs exactly what package-lock.json says and fails if the lockfile is out of date. Set timeout-minutes so a hung test can't burn your whole allowance, and a permissions block so the token is read-only.
The code
name: Node CI
on:
push:
branches: [main]
pull_request:
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm run lint
- run: npm run build --if-present
- run: npm test
Output
actionlint: no problems found workflow: Node CI triggers: push (branches: main), pull_request jobs, in the order they run: build: runs on ubuntu-latest | 6 step(s)
Run this on your computer — see “Get Started” for setup.
The cache key is derived from your lockfile, so the cache refreshes exactly when dependencies change.
Common mistakes
- No lockfile.
cache: npmandnpm ciboth needpackage-lock.jsoncommitted. npm installin CI. It can change the lockfile and hide a drift between your machine and CI.- No
timeout-minutes. The default is 360 minutes, long enough to burn through a free allowance on one stuck job. - Caching
node_modulesdirectly. Cache the package manager's download cache instead; thesetup-*actions already do this safely.
Key points
- CI follows checkout, setup, cache, install, lint/build/test.
cache:onsetup-*actions is the easiest dependency cache.- Use
npm ciand settimeout-minutesand read-onlypermissions. Further reading: GitHub Docs.
Practice questions
Why is `npm ci` preferred over `npm install` in a CI workflow?
Answer
npm ci installs exactly the versions in package-lock.json, is repeatable and fails if the lockfile and package.json disagree. npm install may update the lockfile, hiding differences between CI and your machine.
What is the cache key in `cache: npm` based on?
Answer
The hash of the lockfile (package-lock.json). When dependencies change, the key changes and a fresh cache is created.
Open in the app to mark lessons complete, take notes and track your progress.