node.jsdockeralpine-linuxdebian-based

Different docker base when building and deploying


I'm using dockerized node, and in order to minimize the image as much as possible, I'm mixing between bookworm (Debian 12) and Alpine, in the following way:

FROM node:18.17.1-bookworm-slim AS build
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:18.17.1-alpine AS production
COPY --chown=node:node --from=build /app/node_modules ./node_modules
COPY --chown=node:node --from=build /app/dist ./
COPY --chown=node:node --from=build /app/package*.json ./
USER node
EXPOSE 3000
CMD ["node", "src/app.js"] 

What is the risk I'm taking here by building my node app in Debian and deploying it in Alpine?


Solution

  • There are a couple of notable differences between Alpine- and Debian-based images. One of the most notable is that the shared system C library libc.so has a completely different implementation. Alpine images use the smaller musl libc, where Debian images use the more full-featured GNU libc. This in turn may have some implications on the node binary itself.

    If the node_modules tree contains any libraries that work by compiling C extensions, these may just break trying to copy them from a Debian base to an Alpine base, or vice versa. You'll probably see some sort of weird load-time dependency, maybe something mentioning a GLIBC_2.30 version constraint or similar.

    Using a multi-stage build is fine here, but you should probably use the same base Linux distribution in every stage; especially don't mix Alpine and anything else. If you are in the state where you potentially have compiled native extensions it may be important to use the exact same starting image in both stages (caveat: I know this matters for Python, less practical experience for Node).

    You could use a shared base stage to only declare that base image once

    FROM node:18.17.1-bookworm-slim AS base
    # empty
    
    FROM base AS build
    ...
    
    FROM base
    ...