Builds Multi-Estágio
Separe os estágios de build e runtime para que compiladores TypeScript, test runners e devDependencies nunca sejam enviados para produção.
Receita
Cartão de receita de referência rápida - pronto para copiar e colar.
# 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"]Quando usar isso: Toda imagem de API Node.js. Multi-estágio é o padrão para serviços TypeScript no Node 24.
Exemplo de Trabalho
# syntax=docker/dockerfile:1
FROM node:24-bookworm-slim AS base
WORKDIR /app
# ---- dependências (camada de cache) ----
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
# ---- runtime de produção ----
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"
}
}O que isso demonstra:
- O estágio
depsarmazena em cachenpm ciquando apenas o código fonte muda. - O estágio
buildcompila TypeScript com devDependencies completas. - O estágio
prodcopia apenasdist/enode_modulesde produção. USER nodeapóschownpara runtime não-root.
Análise Detalhada
Como Funciona
- Cada
FROMinicia um novo estágio; apenas artefatosCOPY --from=cruzam os limites do estágio. - Estágios anteriores são descartados da imagem final, exceto pelos arquivos copiados.
- Cache de camadas: coloque etapas lentas (
npm ci) antes de etapas que mudam rapidamente (COPY src).
Padrões de Nomenclatura de Estágio
| Estágio | Propósito | Na imagem final? |
|---|---|---|
deps | Instalar todas as dependências | Não |
build | tsc, bundlers, testes | Não |
prod / runtime | Executar node dist/main.js | Sim |
dev | tsx watch para compose local | Apenas com --target dev |
Montagens de Cache BuildKit (Opcional)
RUN --mount=type=cache,target=/root/.npm \
npm ci --omit=devAcelera reconstruções de CI quando o arquivo de lock não muda. Requer BuildKit (DOCKER_BUILDKIT=1).
Variante 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"]Copie apenas o pacote do workspace que você implanta. Não copie todo o monorepo para o estágio de runtime.
Armadilhas
npm cide estágio único e depois excluir devDeps manualmente - fácil de perder arquivos. Correção: use um estágioproddedicado comnpm ci --omit=dev.- Copiar
/appinteiro do estágio de build - arrastasrc/, testes e caches para a produção. Correção:COPY --from=build /app/dist ./distapenas. - Módulos nativos compilados no estágio errado -
bcryptcompilado no macOS, executado no Linux. Correção: compile dentro do estágio de build do Linux. - Esquecer
package-lock.jsonno estágio de produção -npm cifalha ou instala versões erradas. Correção: copie o arquivo de lock para cada estágio que executanpm ci. - Executar como root no estágio final - scanners de segurança sinalizam isso. Correção:
chown+USER node- veja Containers Não-Root. - Contexto de build enorme - CI lenta. Correção:
.dockerignore- veja Redução de Imagem.
Alternativas
| Alternativa | Use Quando | Não Use Quando |
|---|---|---|
CI compila dist/, Docker apenas copia artefatos | Builds Docker rápidos; compilação TS já no pipeline | Você precisa de builds herméticos inteiramente dentro do Docker |
| esbuild/swc bundle para arquivo único | Imagens minúsculas; bundles serverless | Você precisa de import() dinâmico de muitos arquivos locais |
| Estágio final Distroless | Superfície de ataque mínima | Você precisa de um shell para depuração |
| Imagem de desenvolvimento de estágio único | Protótipos rápidos | Implantações de produção |
FAQs
Quantos estágios eu preciso?
Mínimo de dois: build e prod. Três (deps, build, prod) melhoram a taxa de acerto do cache em projetos grandes.
Devo executar testes na build do Docker?
Execute testes em CI antes de docker build. Opcionalmente, adicione um estágio test que falhe a build, mas a maioria das equipes gerencia isso no GitHub Actions.
Posso usar `npm prune` em vez de um segundo `npm ci`?
Sim, no estágio de build. Para produção, um npm ci --omit=dev novo é mais claro e evita erros de prune.
O NestJS precisa de um padrão diferente?
Mesmo padrão: nest build no estágio de build, node dist/main.js em produção. Veja a documentação de implantação do NestJS para variantes de monorepo.
E quanto a `pnpm` ou `yarn`?
Substitua npm ci por pnpm install --frozen-lockfile ou yarn install --immutable. Mantenha a mesma separação de estágios.
Como depuro um estágio de build com falha?
docker build --target build -t api:debug .
docker run -it --entrypoint sh api:debugRelacionados
- Noções Básicas de Docker - primeiro contêiner
- Redução de Imagem -
.dockerignoree dieta de camadas - Trade-offs Distroless & Alpine - bases de runtime mínimas
- Containers Não-Root - permissões no estágio final
- Melhores Práticas do Docker - checklist da seção
Versões da Stack: Esta página foi escrita para Node.js 24.18.0 (Active LTS), npm 10+, TypeScript 5.6+, Express 5, Fastify 5 e NestJS 11.