The idea is:
Strip non-runtime files — sourcemaps (.map), TypeScript type definitions (.d.ts), raw .ts sources, test files, and markdown — from the compiled node_modules tree during the Docker image build. Concretely, add a pruning step right after the pnpm --filter=n8n --prod --legacy deploy ./compiled call in scripts/build-n8n.mjs (or as a follow-up before the Docker build). Node.js never reads any of these files at runtime, so this wouldn’t change behavior — only the shipped file count/size.
My use case:
I self-host n8n on a small GCP e2-micro VM with a 10GB pd-balanced disk, auto-deployed via GitHub Actions whenever Dependabot bumps the n8nio/n8n tag in docker-compose. Over the last several version bumps, the deploy step (docker compose pull) has taken 9–15 minutes just to extract a single image layer, even though that same layer downloads in under 10 seconds. This repeats on every version bump, since it’s the one layer that always changes (COPY ./compiled /usr/local/lib/node_modules/n8n in docker/images/n8n/Dockerfile).
I think it would be beneficial to add this because:
I timed the actual deploy log (n8n 2.30.5 → 2.31.5): the layer downloaded in ~7s but took 567s (~9.5 min) to extract. So I pulled n8nio/n8n:2.31.5 and inspected that layer’s contents directly:
- ~187,000 filesystem entries (159,220 files + 21,956 dirs + 5,767 symlinks), 1.33GB uncompressed
- Breakdown by file type:
| Type | Count | Size | Needed at runtime? |
|---|---|---|---|
| .js / .cjs / .mjs | 55,033 | 452.5MB | Yes |
| .json | 4,987 | 37.6MB | Yes |
| .map (sourcemaps) | 35,223 | 398.6MB | No |
| .d.ts (type defs) | 34,395 | 97.5MB | No |
| .ts (raw source) | 11,715 | 34.6MB | Mostly no |
| test files | 2,063 | 12.5MB | No |
| .md | 2,359 | 18.8MB | No |
Sourcemaps and type definitions alone account for ~37% of both the file count and the byte size of this layer — none of it code n8n wrote; it ships as-is inside third-party npm packages, and pnpm deploy --legacy copies production dependencies verbatim without pruning.
Layer extraction cost is driven mainly by the number of filesystem operations (create/chmod/symlink), not raw bytes. On fast local NVMe this is invisible, but on network-attached block storage plus a shared/burstable-CPU instance — a common way to self-host n8n cheaply (e2-micro, small VPS, Raspberry Pi) — ~187k small-file operations for one layer can take several minutes even though the useful payload transferred in seconds.
I checked existing GitHub issues and this forum for prior discussion of this specific angle and didn’t find one.
Any resources to support this?
Happy to share the raw analysis (docker export of the layer + file-type breakdown script) if useful for verification.
Are you willing to work on this?
Yes — I’d like to build this, but would appreciate a maintainer sanity-check on the approach first (i.e. is a post-pnpm deploy pruning step acceptable, or is there a reason these files are intentionally kept) before I put together a PR.
Disclosure: I used Claude (Claude Code) to help pull the image, parse the deploy log timings, and tabulate the file-type breakdown above. The investigation approach and conclusions are my own.