AZ.dev

Reproduce Railway's npm ci locally with the exact npm version

When a Railway deploy fails with npm ci errors that don’t reproduce locally, the cause is almost always a different npm version. Pin Railway’s exact npm and run a dry-run to surface the same validation locally.

The Commands

# 1. Find Railway's npm version
railway ssh
# inside the container:
npm -v
exit

# 2. Replay Railway's npm ci against your current lock file
npx -y [email protected] ci --dry-run

# 3. Replay it in production mode (matches how Railway actually installs)
npx -y [email protected] ci --omit=dev --dry-run

Replace 10.9.7 with whatever npm -v printed inside the container. The npx -y npm@<version> form fetches that exact npm without touching your global install.

Why This Works

npm ci --dry-run runs the same EUSAGE strict-lock validation that produces errors like Missing X from lock file, but stops before any disk writes. If the dry-run passes, the real install will pass. If it fails, you see the exact error Railway would emit.

--omit=dev matters because Railway sets NODE_ENV=production, which implicitly triggers --omit=dev. Some lock-file inconsistencies only surface in production mode because they involve dev-only branches of the dependency graph.

The Gotcha

Running npm install locally is not a substitute. npm install rewrites the lock to fit your local resolution; npm ci refuses to and errors out. A clean npm install followed by git status showing no changes is not proof that npm ci will pass elsewhere. Different npm versions compute different “ideal trees”, especially around optional peer dependencies (peerDependenciesMeta: { foo: { optional: true } }). What npm 11 prunes as unneeded, npm 10 may insist must be installed.

When to Reach For This

Related

If you find optional peer dependencies are the source of churn, the fix is usually upstream: wait for the package author to widen the peer range, or pin a different version of the parent package. Adding the transitive dep to your own package.json works as a last resort but adds noise.

npm npm-ci railway railpack package-lock deploy peer-dependencies

Was this helpful?

Related Entries

Serialize per-user check-then-write flows with pg_advisory_xact_lock(hashtext(id))Content-hash dedup that restores a soft-deleted row must re-run quota and limit checksA storage URL builder and its static file handler must share one prefix constantFix "Cannot find module" with ES modules in Node.js