## TL;DR
npm cache corruption in Docker builds usually is not corruption at all: it is a lockfile that drifted from package.json, a stale layer cache, or a half-written node_modules from a killed build. Verify with `npm cache verify`, rebuild the dependency layer without cache, and pin the lockfile into the image build. If the failure mentions integrity checksums, the lockfile and the registry disagree, and the lockfile wins the investigation.

## Error / query
```text
npm cache corruption in Docker builds: how to fix
```

## Use this skill when
- `npm ci` fails in Docker with EINTEGRITY or checksum errors
- The same install works on a developer machine
- node_modules in the image is incomplete or inconsistent
- Failures are intermittent across builds

## Not for this skill when
- npm cannot reach the registry at all (network/auth)
- The Dockerfile has a syntax error (build problem)
- The app fails at runtime with missing modules (different diagnosis)

## Steps

### Step 1: Reproduce with a clean dependency layer
```dockerfile
COPY package.json package-lock.json ./
RUN npm ci --no-audit --no-fund
```
Expected: copying only the manifests first isolates the dependency layer. If this layer fails, the problem is in the manifests or registry, not your app code. Build with `--no-cache-filter` on this stage to rule out stale layers.

### Step 2: Verify the npm cache and clear it if corrupt
```bash
docker build --progress=plain --target deps -t debug:deps . 2>&1 | grep -i "integrity\|corrupt\|EINTEGRITY" | head -5
```
Expected: integrity errors name the exact package and expected vs actual hash. If the cache is genuinely corrupt, add `npm cache clean --force` before `npm ci` in the Dockerfile, or better, stop persisting the npm cache between builds and let `npm ci` work from the lockfile alone.

### Step 3: Check for lockfile drift
```bash
git diff --stat package.json package-lock.json
npm ci --dry-run 2>&1 | head -5
```
Expected: package.json and package-lock.json change together. Drift (package.json edited without regenerating the lockfile) makes `npm ci` fail deterministically while `npm install` works locally, which looks exactly like cache corruption.

### Step 4: Use a BuildKit cache mount instead of layer caching for npm
```dockerfile
RUN --mount=type=cache,target=/root/.npm npm ci --no-audit --no-fund
```
Expected: the npm cache lives in a BuildKit cache mount, shared across builds but isolated from image layers. Corruption in one build does not poison the layer cache, and you can bust it independently when needed.

## Variant phrasings

### "npm ci EINTEGRITY docker"
Integrity errors mean lockfile vs registry disagreement. Step 3 first.

### "npm install fails only in docker build"
Steps 1 and 3. Local `npm install` tolerates drift that `npm ci` in Docker does not.

## Why it happens
`npm ci` is strict by design: it deletes node_modules and installs exactly what the lockfile says, verifying every checksum. Local `npm install` is lenient and heals drift silently. Docker layer caching then freezes whichever result the first build produced, so a transient registry hiccup or a drifted lockfile gets baked into the cache and replayed as `corruption` on every subsequent build.

## Edge cases and pitfalls
- Private registries with flaky proxies serve truncated tarballs that fail integrity checks intermittently; the fix is the proxy, not npm.
- `npm ci` needs the lockfile present; a .dockerignore that excludes it produces a confusing `requires existing lock file` error.
- Node version drift between local and image changes which binaries get built; pin the base image tag.
- Cache mounts are per-builder; a fresh CI runner starts with an empty mount, so the first build after rotation is always slow.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_zLG0--6FeTGDSbP-ydwj4g
