Skip to Content
Stop Shipping node_modules in the Image
Intermediate
0 likes
Tutorials

Stop Shipping node_modules in the Image

Keep node_modules out of the build context with a .dockerignore file and let npm ci install the packages inside the image. After this, you can cut the context size and avoid copying Windows-built packages into a Linux image.

If your Node project has a node_modules folder on your computer, a plain COPY . . in the Dockerfile sends it into the image, on top of what npm ci just installed. The folder is big, it may hold files built for Windows, and it makes every build slower. This page shows the problem with numbers and the two-line fix.

The setup

A tiny project with one real dependency, and a node_modules folder that stands in for a heavy one: 1,500 small folders. Create the lock file with a throwaway container, so you do not need Node on your computer.

{
  "name": "nm-demo",
  "version": "1.0.0",
  "dependencies": {
    "is-number": "7.0.0"
  }
}
const isNumber = require("is-number");
console.log("is-number says 5 is a number:", isNumber(5));
FROM node:24-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
CMD ["node", "index.js"]
Set-Location nm-demo
docker run --rm -v "${PWD}:/app" -w /app node:24-alpine npm install --package-lock-only --silent
New-Item -ItemType Directory node_modules | Out-Null
$bytes = New-Object byte[] 16KB
1..1500 | ForEach-Object { $d = "node_modules\pkg$_"; New-Item -ItemType Directory $d | Out-Null; [IO.File]::WriteAllBytes("$PWD\$d\index.js", $bytes) }
(Get-ChildItem node_modules).Count
1500

Build without a .dockerignore

docker build --progress=plain -t nm-demo:1 . 2>&1 | Select-String "transferring context: \d+\.\d+MB" | ForEach-Object { $_.ToString().Trim() }
docker run --rm nm-demo:1 sh -c "ls node_modules | wc -l"
docker run --rm nm-demo:1
#5 transferring context: 24.73MB 1.7s done
1501
is-number says 5 is a number: true

The builder received about 25 MB for a program that is two lines long. The image has 1,501 folders in node_modules: the one that npm ci installed and the 1,500 that came from your computer. It still runs, which is why nobody notices. In a real project the copied folder can hold packages compiled for Windows. They sit next to the Linux ones, or replace them, and the failure shows up later as a strange "invalid ELF header" error.

The fix

Create .dockerignore and list the folder. Two lines are enough here.

Set-Content .dockerignore "node_modules`n.git"
docker build --progress=plain -t nm-demo:2 . 2>&1 | Select-String "transferring context: \d+(\.\d+)?[kM]?B" | ForEach-Object { $_.ToString().Trim() }
docker run --rm nm-demo:2 sh -c "ls node_modules | wc -l"
docker run --rm nm-demo:2
#3 transferring context: 59B done
#5 transferring context: 183B 0.1s done
1
is-number says 5 is a number: true

The context dropped to a few hundred bytes, and node_modules in the image has the one package npm ci installed. Dependencies now come from the lock file, inside the image, built for the image's own platform. That is the point of npm ci.

The Dockerfile copies package*.json first, runs npm ci, and only then copies the rest. Docker reuses the install step as long as those two files do not change, so editing index.js does not download the packages again.

⚠ Warning: Ignoring node_modules does not stop you from copying it on purpose with a line such as COPY node_modules ./node_modules. If a build says the file is not found after you added the ignore line, look for a line like that.

The same applies to other folders that should never travel: .git, build output such as dist, .env files and editor settings. The course lesson on the build context has the full list and the pattern rules.

Related course: Docker Intermediate.

About

Keep node_modules out of the build context with a .dockerignore file and let npm ci install the packages inside the image. After this, you can cut the context size and avoid copying Windows-built packages into a Linux image.

Responsible
Christopher R
Last Update
08/12/2026
Reading Time
4 min
Was this helpful?
​ 0 ​ 0

Comments (0)

Sign in to comment

No comments yet.