Safeguard
Developer Tools•

Why does npm install fail on node alpine and how do I fix it safely?

Native add-ons need a compiler that alpine images lack. Install the toolchain in a build stage only, so it never reaches production. Steps and trade-offs.

Safeguard Security Team
3 min read

Packages with native add-ons need a build toolchain such as Python, make and g++, which the node alpine image does not include. Install those tools in a separate build stage, compile there, and copy only the finished node_modules into the final stage, so the compilers never ship to production.

What is actually happening

Alpine images are small on purpose. The official Node.js image documentation describes the alpine variant as useful when final image size is your primary concern, with the caveat that it uses musl libc instead of glibc, so software may run into issues depending on its libc assumptions. It also notes that extra tools such as git or bash are uncommon in Alpine based images.

When an npm package contains C or C++ code, npm runs node-gyp to compile it. That needs Python, make and a C++ compiler. Without them the install fails, often with a long error that mentions node-gyp.

Two fixes, one better

The Node.js maintainers document both of these.

Option one installs the toolchain as a virtual package, runs the install, then removes the toolchain in the same layer:

FROM node:alpine
RUN apk add --no-cache --virtual .gyp python3 py-setuptools make g++ \
 && npm install \
 && apk del .gyp

Option two uses a multi-stage build. The toolchain lives only in the builder:

FROM node:alpine AS builder
RUN apk add --no-cache python3 make g++
WORKDIR /app
COPY package*.json ./
RUN npm ci
FROM node:alpine AS app
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY . .

The second is cleaner to read and audit. The compilers are not in any layer of the final image, and a reviewer can see that at a glance.

Check whether you need the native build at all

Many packages ship prebuilt binaries for common platforms. Those binaries are usually built against glibc, so they may not load on musl. That mismatch is a frequent reason alpine installs behave differently from a Debian slim install. If a native dependency causes repeated trouble, a Debian based slim image is a legitimate answer rather than a failure.

SituationReasonable choice
Pure JavaScript dependenciesAlpine or slim, either works
A few native add-ons, builds fineAlpine with a builder stage
Native add-ons with glibc only binariesDebian slim

A security angle

Leaving python3 and g++ in the final image gives an attacker compilers. It also adds packages that scanners report, which you then triage for software the app never uses. Keeping them out is a small win for both.

Where Safeguard fits

Whichever base you pick, you need to know what is inside it. Safeguard builds an SBOM and scans dependencies, with reachability analysis to separate findings in code you call from findings in code you do not. The free scan takes a repository and returns a first dependency view.

Quick check

Does the slim image avoid this problem?

Not automatically. The slim variants contain only the packages needed to run Node.js, so compiling native add-ons still needs a toolchain. The difference is glibc compatibility with prebuilt binaries.

Is deleting the toolchain in the same RUN enough?

It keeps that layer clean, but a multi-stage build is easier to verify because the final image never contains the tools in any layer.

Sources

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.