Production Docker image ships ~40% non-runtime files (sourcemaps/.d.ts) — slows down pulls on constrained hosts [GOT CREATED]

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.

1 Like

Hey, thanks for the suggestion!

I did a quick benchmark on my local laptop (under load) and it looks promising, opened a PR with the data, hope you don’t mind :folded_hands:

We do need the sourcemaps for accurate error reporting, but file count is the bottleneck here rather than size, so this is a great gain anyway.

2 Likes

Thanks for taking a look, and for the quick turnaround on #35170 — really appreciate it!

Good call keeping .js.map around; I’d lumped all sourcemaps together in my “not needed at runtime” bucket, but you’re right that production stack traces need them. Glad the file-count angle was useful either way, and nice to see the numbers land close to what I estimated (-31.5% entries, -38% extraction time).

That’s exactly the improvement I was hoping for — no further ask from my side. Thanks again for picking this up so fast!

1 Like

New version n8n@2.34.0 got released which includes the GitHub PR 35170.

2 Likes