Construir una aplicación Node
El objetivo para un servicio Node es una imagen final que lleve tu salida construida y tus node_modules de producción, y nada más. Sin dependencias de desarrollo, sin código fuente que no envías, sin caché del gestor de paquetes. Una construcción de tres etapas te lleva ahí: resuelve las dependencias de producción una vez, construye con el conjunto completo de dependencias en paralelo y luego ensambla una etapa de runtime limpia a partir de las dos.
Estas construyen sobre ghcr.io/quenchworks/images/node:24. También existe ghcr.io/quenchworks/images/pnpm:10, una base node con pnpm ya habilitado a través de corepack, que ahorra el paso corepack enable en la variante pnpm.
El Dockerfile multi-etapa
# Build stage: pnpm is already enabled on the pnpm base image.FROM ghcr.io/quenchworks/images/pnpm:10 AS buildWORKDIR /app
# Resolve production dependencies on their own so the runtime stage can# copy just these. A cache mount keeps the pnpm store warm across builds.COPY package.json pnpm-lock.yaml ./RUN ["pnpm", "install", "--prod", "--frozen-lockfile"]RUN ["cp", "-r", "node_modules", "/prod_modules"]
# Now install everything (incl. dev deps) and build.RUN ["pnpm", "install", "--frozen-lockfile"]COPY . .RUN ["pnpm", "run", "build"]
# Runtime stage: a slim node base, nonroot, with only prod deps + dist.FROM ghcr.io/quenchworks/images/node:24 AS runtimeWORKDIR /appENV NODE_ENV=productionCOPY --from=build /prod_modules ./node_modulesCOPY --from=build /app/dist ./distCOPY --from=build /app/package.json ./package.jsonUSER 1001EXPOSE 3000CMD ["node", "dist/server.js"]FROM ghcr.io/quenchworks/images/node:24 AS buildWORKDIR /app
# Production dependencies only, into their own copy.COPY package.json package-lock.json ./RUN ["npm", "ci", "--omit=dev"]RUN ["cp", "-r", "node_modules", "/prod_modules"]
# Full install (incl. dev deps) and build.RUN ["npm", "ci"]COPY . .RUN ["npm", "run", "build"]
FROM ghcr.io/quenchworks/images/node:24 AS runtimeWORKDIR /appENV NODE_ENV=productionCOPY --from=build /prod_modules ./node_modulesCOPY --from=build /app/dist ./distCOPY --from=build /app/package.json ./package.jsonUSER 1001EXPOSE 3000CMD ["node", "dist/server.js"]# Yarn (Classic) is preinstalled on the yarn base image.FROM ghcr.io/quenchworks/images/yarn:1 AS buildWORKDIR /app
# Production dependencies only.COPY package.json yarn.lock ./RUN ["yarn", "install", "--frozen-lockfile", "--production"]RUN ["cp", "-r", "node_modules", "/prod_modules"]
# Full install (incl. dev deps) and build.RUN ["yarn", "install", "--frozen-lockfile"]COPY . .RUN ["yarn", "build"]
FROM ghcr.io/quenchworks/images/node:24 AS runtimeWORKDIR /appENV NODE_ENV=productionCOPY --from=build /prod_modules ./node_modulesCOPY --from=build /app/dist ./distCOPY --from=build /app/package.json ./package.jsonUSER 1001EXPOSE 3000CMD ["node", "dist/server.js"]Qué hace cada etapa
- Dependencias de producción. Instala solo las dependencias de producción y luego cópialas a una ruta fija (
/prod_modules). Este es el conjunto que la imagen final necesita, mantenido aparte de las dependencias de desarrollo que la construcción incorpora a continuación. - Construcción. Instala el conjunto completo de dependencias, copia el código fuente y ejecuta la construcción. Esta etapa contiene el compilador, las dependencias de desarrollo y el código fuente, ninguno de los cuales se entrega.
- Runtime. Empieza de nuevo desde
node:24, copia losnode_modulesde producción y eldist/construido, baja auid 1001y arranca. El conjunto de herramientas se queda atrás.
Siguiente
- ¿Ejecutas un frontend estático en lugar de un servidor Node? Construye con
node, luego sirve los recursos construidos desdenginx, o entrega un binario autocontenido sobrestatic. - Fija por digest una vez que te decidas por una base, para que cada construcción ejecute exactamente lo que se escaneó y firmó.