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.

Create a free account to save this page, take notes and track your progress. Sign in

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:

  1. Check out the code (actions/checkout).
  2. Set up the toolchain (actions/setup-node, setup-python, setup-java, setup-go...).
  3. Restore a cache so dependencies aren't downloaded each time. The setup-* actions can do this for you with cache: npm (or pip, maven, gradle).
  4. 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

Node.js CI with caching
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: npm and npm ci both need package-lock.json committed.
  • npm install in 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_modules directly. Cache the package manager's download cache instead; the setup-* actions already do this safely.

Key points

  • CI follows checkout, setup, cache, install, lint/build/test.
  • cache: on setup-* actions is the easiest dependency cache.
  • Use npm ci and set timeout-minutes and read-only permissions. 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.

Was this page helpful?