snpmv2026.5.16

CI/CD

Use snpm in continuous integration and deployment pipelines

snpm is built for CI: deterministic installs, a hot path that exits in milliseconds when nothing changed, parallelism that scales with available bandwidth, and SARIF audit output for security tabs.

Quick start

# Install snpm
npm install -g snpm

# Reproducible install
snpm install --frozen-lockfile

# Build / test
snpm run build
snpm run test

In a monorepo:

snpm install --frozen-lockfile
snpm run build -r
snpm run test --filter "[origin/main]"   # only what changed

GitHub Actions

.github/workflows/ci.yml
name: CI

on: [push, pull_request]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: Install snpm
        run: npm install -g snpm

      - name: Cache snpm store
        uses: actions/cache@v4
        with:
          path: ~/.local/share/snpm
          key: ${{ runner.os }}-snpm-${{ hashFiles('**/snpm-lock.yaml') }}
          restore-keys: |
            ${{ runner.os }}-snpm-

      - name: Install dependencies
        env:
          SNPM_MIN_PACKAGE_AGE_DAYS: "7"
          SNPM_ALLOW_SCRIPTS: "esbuild,sharp"
        run: snpm install --frozen-lockfile

      - name: Build
        run: snpm run build

      - name: Test
        run: snpm run test

Audit with SARIF

      - name: Audit dependencies
        run: snpm audit --format sarif > snpm-audit.sarif
        continue-on-error: true

      - name: Upload SARIF
        uses: github/codeql-action/upload-sarif@v3
        if: always()
        with:
          sarif_file: snpm-audit.sarif

GitLab CI

.gitlab-ci.yml
image: node:20

cache:
  key:
    files:
      - snpm-lock.yaml
  paths:
    - ~/.local/share/snpm

stages:
  - install
  - test

install:
  stage: install
  script:
    - npm install -g snpm
    - snpm install --frozen-lockfile

test:
  stage: test
  variables:
    SNPM_MIN_PACKAGE_AGE_DAYS: "7"
  script:
    - npm install -g snpm
    - snpm install --frozen-lockfile
    - snpm run test

CircleCI

.circleci/config.yml
version: 2.1

jobs:
  build:
    docker:
      - image: cimg/node:20.0
    steps:
      - checkout
      - restore_cache:
          keys:
            - snpm-{{ checksum "snpm-lock.yaml" }}
            - snpm-
      - run: npm install -g snpm
      - run: snpm install --frozen-lockfile
      - run: snpm run build
      - run: snpm run test
      - save_cache:
          paths:
            - ~/.local/share/snpm
          key: snpm-{{ checksum "snpm-lock.yaml" }}

workflows:
  build-and-test:
    jobs:
      - build

Docker

FROM node:20-slim AS deps

WORKDIR /app
RUN npm install -g snpm

COPY package.json snpm-lock.yaml ./
COPY snpm-workspace.yaml* snpm-catalog.yaml* ./
COPY packages packages
COPY apps apps

RUN snpm install --production --frozen-lockfile

FROM node:20-slim
WORKDIR /app
COPY --from=deps /app /app
CMD ["node", "apps/web/dist/index.js"]

For monorepos you can copy only the workspace metadata first to cache the install layer, then copy source files for the build step.

Common patterns

Production install

snpm install --production --frozen-lockfile

Skips devDependencies and refuses to touch the lockfile.

Min package age

SNPM_MIN_PACKAGE_AGE_DAYS=7 snpm install --frozen-lockfile

Refuses to use versions published within the last 7 days. Combine with --frozen-lockfile to be sure CI never silently bumps to a fresh release.

Bumping concurrency

SNPM_REGISTRY_CONCURRENCY defaults to 128. Most CI runners can push it higher on fast networks; lower it on memory-constrained runners or when the registry rate-limits.

Caching the store

CacheWhat to cache
Shared package store~/.local/share/snpm (Linux), ~/Library/Application Support/io.snpm.snpm (macOS) — or whatever snpm config reports as the data dir
Metadata cacheAlready inside the data dir

Key the cache on snpm-lock.yaml so it invalidates when dependencies change. snpm only writes new entries; cache restore is purely additive.

Workspaces

# install everything once
snpm install --frozen-lockfile

# build everything
snpm run build -r

# only run tests for projects whose files changed since main
snpm run test --filter "[origin/main]"

snpm publish -r --filter ./packages publishes every project under packages/ (handy for release pipelines).

Authenticating to a private registry

GitHub Actions example:

- name: Install dependencies
  env:
    SNPM_CONFIG_REGISTRY: https://npm.mycompany.com
    NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
  run: snpm install --frozen-lockfile

Or commit a .snpmrc that interpolates env vars:

.snpmrc
registry=https://npm.mycompany.com/
//npm.mycompany.com/:_authToken=${NPM_TOKEN}

Troubleshooting

--frozen-lockfile fails

The lockfile is out of date. Run snpm install locally, commit the resulting snpm-lock.yaml, and push.

Install scripts blocked

snpm blocks dependency lifecycle scripts by default. Add the trusted package to the allow-list:

SNPM_ALLOW_SCRIPTS="esbuild,sharp" snpm install --frozen-lockfile

After widening the allow-list, run snpm rebuild to apply it to packages that already extracted.

Slow cold installs

  • Make sure the store cache key is correct (hashFiles('**/snpm-lock.yaml')).
  • Bump SNPM_REGISTRY_CONCURRENCY if the network has headroom.
  • Use --prefer-frozen-lockfile on retries to reuse the lockfile but re-resolve only what changed.

Authentication failures

  • Verify the auth token is exported in the job's environment.
  • Confirm snpm config shows the expected default registry and default auth status.
  • For scoped registries, make sure both @scope:registry=... and the matching //host:_authToken=... are set.

On this page