Uma imagem Docker de 1,2GB carrega compiladores, gerenciadores de pacote e devDependencies para servir uma aplicação que precisa de 90MB. Multi-stage builds resolvem isso separando compilação de execução: você constrói em um estágio e copia só o artefato para o estágio final. O app Express deste exemplo caiu de 1,1GB para 150MB com Alpine e para 90MB com distroless, sem mudar uma linha de código.
O que sobra em uma imagem single-stage?
O estágio único mistura o que constrói com o que executa. node_modules completo com devDependencies, gcc, curl e cache do pip entram em produção junto. Cada pacote extra aparece no relatório do Trivy e amplia a superfície de ataque. Pulls de 1GB em cada nó do cluster custam minutos por deploy.
Anatomia de um multi-stage build
O Dockerfile declara dois FROM. O estágio builder instala tudo e compila. O estágio runtime parte de uma base limpa e recebe só o resultado com COPY --from=builder /app/dist ./dist. Camadas do builder jamais chegam à imagem final.
Base image para o runtime
Variantes Alpine encolhem a imagem, e musl libc quebra módulos nativos compilados contra glibc. Teste antes de migrar. Imagens distroless shipam sem shell e sem gerenciador de pacotes, deixando nada para um atacante explorar. A tag :debug devolve busybox e salva o incident response.
Layer caching
A ordem das instruções define o cache. Copie package.json antes do código-fonte, rode a instalação de dependências e só então copie o resto. Uma alteração de código reaproveita as camadas de dependência e transforma builds de minutos em segundos.
Por linguagem
- Node: rode
npm ci --omit=devno estágio final e copie a saída do tsc do builder - Go: compile com
CGO_ENABLED=0e suba um binário estático de ~10MB sobre scratch - Python: mantenha as dependências de build do pip no builder e copie as wheels para o estágio slim
Endurecimento da imagem
Adicione um usuário non-root com USER. Fixe a base por digest (node@sha256:...) em vez de tag móvel. Use COPY --chown para evitar arquivos owned pelo root. Em Kubernetes, monte o root filesystem como read-only.
Verificação do resultado
dive inspeciona camada por camada e aponta desperdício. docker history expõe segredo colado em ARG ou RUN. O Trivy roda no CI e bloqueia o deploy quando CVE alta aparece na imagem final.
O ganho composto aparece no pipeline: pulls dez vezes mais rápidos, scan enxuto e menos pacote para patchear na próxima emergência. Comece pelo Dockerfile mais antigo do monorepo; o de 1,2GB sempre existe.
Curtiu o conteúdo?
Construo produtos web e soluções com IA do jeito certo — arquitetura sólida, código sustentável e entrega real.
Vamos conversar