Compilaciones Multi-Etapa
Separa las etapas de construcción y tiempo de ejecución para que los compiladores de TypeScript, los ejecutores de pruebas y las dependencias de desarrollo nunca se envíen a producción.
Receta
Tarjeta de receta de referencia rápida - lista para copiar y pegar.
# syntax=docker/dockerfile:1
FROM node:24-bookworm-slim AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
FROM deps AS build
COPY tsconfig.json ./
COPY src ./src
RUN npm run build
FROM node:24-bookworm-slim AS prod
WORKDIR /app
ENV NODE_ENV=production
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY --from=build /app/dist ./dist
USER node
EXPOSE 3000
CMD ["node", "dist/main.js"]Cuándo usarlo: Cada imagen de API de Node.js. Multi-etapa es el patrón predeterminado para los servicios TypeScript en Node 24.
Ejemplo de Funcionamiento
# syntax=docker/dockerfile:1
FROM node:24-bookworm-slim AS base
WORKDIR /app
# ---- dependencias (capa en caché) ----
FROM base AS deps
COPY package.json package-lock.json ./
RUN npm ci
# ---- compilar TypeScript ----
FROM deps AS build
COPY tsconfig.json ./
COPY src ./src
RUN npm run build && npm prune --omit=dev
# ---- tiempo de ejecución de producción ----
FROM node:24-bookworm-slim AS prod
WORKDIR /app
ENV NODE_ENV=production
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY --from=build /app/dist ./dist
RUN chown -R node:node /app
USER node
HEALTHCHECK CMD node -e "fetch('http://127.0.0.1:'+(process.env.PORT||3000)+'/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
CMD ["node", "dist/main.js"]{
"scripts": {
"build": "tsc -p tsconfig.json",
"start": "node dist/main.js"
}
}Lo que esto demuestra:
- La etapa
depsalmacena en cachénpm cicuando solo cambian los archivos fuente - La etapa
buildcompila TypeScript con todas las dependencias de desarrollo - La etapa
prodcopia solodist/y losnode_modulesde producción USER nodedespués dechownpara un tiempo de ejecución sin root
Análisis Profundo
Cómo Funciona
- Cada
FROMinicia una nueva etapa; solo los artefactosCOPY --from=cruzan los límites de las etapas - Las etapas anteriores se descartan de la imagen final, excepto los archivos copiados
- Caché de capas: coloca los pasos lentos (
npm ci) antes de los pasos que cambian rápidamente (COPY src)
Patrones de Nomenclatura de Etapas
| Etapa | Propósito | ¿En la imagen final? |
|---|---|---|
deps | Instalar todas las dependencias | No |
build | tsc, bundlers, pruebas | No |
prod / runtime | Ejecutar node dist/main.js | Sí |
dev | tsx watch para composición local | Solo con --target dev |
Montajes de Caché de BuildKit (Opcional)
RUN --mount=type=cache,target=/root/.npm \
npm ci --omit=devAcelera las reconstrucciones de CI cuando el archivo lockfile no ha cambiado. Requiere BuildKit (DOCKER_BUILDKIT=1).
Variante de Monorepo
FROM node:24-bookworm-slim AS build
WORKDIR /app
COPY package.json package-lock.json ./
COPY packages/api/package.json packages/api/
COPY packages/shared/package.json packages/shared/
RUN npm ci
COPY . .
RUN npm run build -w packages/api
FROM node:24-bookworm-slim AS prod
WORKDIR /app
COPY --from=build /app/packages/api/dist ./dist
COPY --from=build /app/node_modules ./node_modules
USER node
CMD ["node", "dist/main.js"]Copia solo el paquete del espacio de trabajo que implementas. No copies todo el monorepo en la etapa de tiempo de ejecución.
Errores Comunes
npm cide una sola etapa y luego eliminar manualmente las dependencias de desarrollo - es fácil pasar por alto archivos. Solución: usa una etapaproddedicada connpm ci --omit=dev.- Copiar todo
/appde la etapa de construcción - arrastrasrc/, pruebas y cachés a producción. Solución:COPY --from=build /app/dist ./distsolamente. - Módulos nativos construidos en la etapa incorrecta -
bcryptcompilado en macOS, ejecutado en Linux. Solución: compilar dentro de la etapa de construcción de Linux. - Olvidar
package-lock.jsonen la etapa de producción -npm cifalla o instala versiones incorrectas. Solución: copia el archivo lockfile a cada etapa que ejecutenpm ci. - Ejecutar como root en la etapa final - los escáneres de seguridad lo marcan. Solución:
chown+USER node- consulta Contenedores sin Root. - Contexto de construcción enorme - CI lento. Solución:
.dockerignore- consulta Reducción de Tamaño de Imagen.
Alternativas
| Alternativa | Usar Cuándo | No Usar Cuándo |
|---|---|---|
CI construye dist/, Docker solo copia artefactos | Construcciones Docker rápidas; la compilación TS ya está en la pipeline | Necesitas construcciones herméticas completamente dentro de Docker |
| esbuild/swc agrupa en un solo archivo | Imágenes pequeñas; paquetes serverless | Necesitas import() dinámico de muchos archivos locales |
| Etapa final Distroless | Superficie de ataque mínima | Necesitas un shell para depuración |
| Imagen de desarrollo de una sola etapa | Prototipos rápidos | Despliegues de producción |
Preguntas Frecuentes
¿Cuántas etapas necesito?
Mínimo dos: build y prod. Tres (deps, build, prod) mejora la tasa de aciertos de caché en proyectos grandes.
¿Debo ejecutar pruebas en la construcción de Docker?
Ejecuta las pruebas en CI antes de docker build. Opcionalmente, agrega una etapa test que falle la construcción, pero la mayoría de los equipos lo controlan en GitHub Actions.
¿Puedo usar `npm prune` en lugar de un segundo `npm ci`?
Sí, en la etapa de construcción. Para producción, un npm ci --omit=dev fresco es más claro y evita errores de prune.
¿NestJS necesita un patrón diferente?
El mismo patrón: nest build en la etapa de construcción, node dist/main.js en producción. Consulta la documentación de despliegue de NestJS para variantes de monorepo.
¿Qué pasa con `pnpm` o `yarn`?
Reemplaza npm ci con pnpm install --frozen-lockfile o yarn install --immutable. Mantén la misma separación de etapas.
¿Cómo depuro una etapa de construcción fallida?
docker build --target build -t api:debug .
docker run -it --entrypoint sh api:debugRelacionado
- Conceptos Básicos de Docker - primer contenedor
- Reducción de Tamaño de Imagen -
.dockerignorey dieta de capas - Compensaciones de Distroless y Alpine - bases de tiempo de ejecución mínimas
- Contenedores sin Root - permisos en la etapa final
- Mejores Prácticas de Docker - lista de verificación de la sección
Versiones de Stack: Esta página fue escrita para Node.js 24.18.0 (LTS Activo), npm 10+, TypeScript 5.6+, Express 5, Fastify 5 y NestJS 11.