Docker multi-stage: a imagem que não leva o que não usa
Uma imagem Docker de uma aplicação Node feita à pressa leva lá dentro o compilador de TypeScript, as ferramentas de teste, o node_modules de desenvolvimento inteiro e o código-fonte. Nada disso corre em produção — mas tudo isso é descarregado a cada deploy, e tudo isso é superfície de ataque.
O multi-stage build resolve isto com uma ideia simples: uma imagem para construir, outra para correr, e da primeira só passa para a segunda o que interessa.
1. O antes
FROM node:20-alpine
WORKDIR /app
COPY . .
RUN npm install
RUN npm run build
CMD ["node", "dist/servidor.js"]Funciona. E leva para produção o código-fonte, os testes, o TypeScript e tudo o que o npm install trouxe para construir.
2. O depois
# --- Etapa 1: construir ---
FROM node:20-alpine AS build
WORKDIR /app
# Copiar primeiro os manifestos: enquanto eles não mudarem, o Docker
# reaproveita a camada das dependências e salta o npm ci todo.
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build
# --- Etapa 2: correr ---
FROM node:20-alpine AS runtime
WORKDIR /app
ENV NODE_ENV=production
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
# Só o resultado da compilação atravessa
COPY --from=build /app/dist ./dist
# O node:alpine já traz um utilizador 'node'. Correr como root dentro do
# contentor é um privilégio que a aplicação não precisa.
USER node
CMD ["node", "dist/servidor.js"]3. Onde estão mesmo os ganhos
Tamanho: tudo o que ficou na primeira etapa é deitado fora. A imagem final tem o Node, as dependências de produção e o
dist.Cache: copiar o
package-lock.jsonantes do resto do código é o truque que mais tempo poupa. Mudar uma linha de código não volta a instalar dependências nenhumas.Segurança: o que não está na imagem não tem vulnerabilidades. E o
USER nodeé uma linha.
4. E o .dockerignore
Sem ele, o COPY . . manda para dentro do contexto de build o node_modules local, o .git inteiro e — pior — o .env:
node_modules
.git
.env
dist
*.logVale a pena olhar para ele antes de olhar para o Dockerfile. É o ficheiro que decide o que sequer chega a ser considerado.