npm in CI/CD

What is CI/CD?

CI/CD means a service automatically runs your project's commands whenever you push code — installing packages, building, testing, and often deploying. Instead of running npm run build on your laptop and uploading by hand, a robot does the exact same steps, the same way, every time. GitHub Actions, GitLab CI and Cloudflare Pages are common examples.

Rule #1: use npm ci, not npm install

In automation you want a clean, identical install every run. That's exactly what npm ci does (from the versioning chapter): it wipes node_modules and installs the package-lock.json precisely, never changing it. It's faster than npm install and can't drift.

The standard CI sequence

Terminal
$ npm ci            # exact, clean install from the lockfile
$ npm run build     # build the project
$ npm test          # run the tests (if any)
In VS Code
  1. These are the commands a CI service runs for you
  2. You can run the same three locally to reproduce exactly what CI does
  3. npm ci needs a committed package-lock.json — which is why you always commit it

If npm ci errors that the lockfile is out of sync, run npm install locally, commit the updated lockfile, and push.

Rule #2: pin the Node version

CI should build with the same Node version your project expects (this one wants >=22.12.0). You tell the CI which Node to use — and turn on npm caching so installs are quick:

- uses: actions/setup-node@v4
  with:
    node-version: 22
    cache: npm        // caches ~/.npm between runs for speed

A .nvmrc file (containing just a version like 22) is another common way to record the intended Node version so both humans and CI agree.

A complete, real example

Here's a GitHub Actions workflow that installs, builds and deploys a static site like this one to Cloudflare Pages — mirroring this project's own deploy script (astro build && wrangler pages deploy dist). It lives at .github/workflows/deploy.yml:

name: Deploy
on:
  push:
    branches: [master]
jobs:
  build-and-deploy:
    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 run build
      - run: npx wrangler pages deploy dist --project-name=siazly
        env:
          CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}

Read it top to bottom: on every push to master, a fresh Ubuntu machine checks out the code, sets up Node 22 with npm caching, runs npm ci, builds, then deploys — using a secret token kept in the repo's settings, never in the code.

Secrets, not hard-coded keys: the ${{ secrets.CLOUDFLARE_API_TOKEN }} reads a value you store in GitHub → Settings → Secrets. The same golden rule as the .npmrc chapter: tokens live in secrets or environment variables, never committed.

Auditing and publishing in CI

A couple of npm commands earn their place in pipelines:

  • npm audit --audit-level=high — fail the build if a high-severity vulnerability appears, so security issues can't sneak in unnoticed.
  • npm publish --provenance — when publishing a package from CI, this attaches a verifiable record of where and how it was built, which users can trust.

Good news: many hosts (including Cloudflare Pages and Netlify) have CI built in — you just tell them your build command (npm run build) and they run npm ci + build for you on every push, no workflow file required.

Up next: the cheat sheet — every command from this guide on one page.

FAQ

Frequently Asked Questions

Should I use npm install or npm ci in CI?
Use npm ci. It wipes node_modules and installs the package-lock.json exactly, so every run is clean and identical. It's faster than npm install and never changes the lockfile.
Why does npm ci need a package-lock.json?
Because it installs that file exactly. If the lockfile is missing or disagrees with package.json, npm ci errors out — which is itself a useful warning that something is out of sync.
How do I make npm faster in CI?
Cache the npm download folder between runs — for GitHub Actions, actions/setup-node with cache: 'npm' does it. Also pin the Node version so installs are consistent.
Do I need a workflow file just to deploy a site?
Often not. Hosts like Cloudflare Pages and Netlify have CI built in — you give them your build command (npm run build) and they run npm ci and build on every push for you.