Docker BuildKit com Multi-Stage: Reduzindo Tamanho de Imagem em Produção
Como BuildKit e multi-stage builds transformam containers inchados em imagens de produção eficientes, reduzindo custo de armazenamento e acelerando deploys.
Resumo executivo
Como BuildKit e multi-stage builds transformam containers inchados em imagens de produção eficientes, reduzindo custo de armazenamento e acelerando deploys.
Ultima atualizacao: 17/03/2026
O problema do container inchado
Um container de produção Node.js que deveria pesar 50MB pode facilmente acabar com 500MB+, 1GB ou mais. Isso acontece quando dependências de build, ferramentas de desenvolvimento, caches e artefatos temporários são incluídos na imagem final.
Imagens grandes significam:
- Deploy mais lento: 500MB demora 10x mais para fazer pull do que 50MB
- Custo mais alto: armazenamento em registry custa dinheiro
- Superfície de ataque maior: mais pacotes instalados = mais vulnerabilidades
- Cache menos eficiente: pequenas mudanças invalidam grandes camadas
Multi-stage builds com BuildKit resolvem isso permitindo que você use múltiplos Dockerfiles em um único build, copiando apenas artefatos necessários para a imagem final.
BuildKit: O novo motor de build
BuildKit é o próximo motor de build do Docker, habilitado por padrão desde Docker 18.09. Ele oferece:
- Execução paralela de stages: stages independentes rodam simultaneamente
- Caching inteligente: cache granular por instruções, não por camada inteira
- Build secrets: passar secrets sem incluí-los na imagem final
- Inline caching: cache remoto para builds de CI/CD
Habilitar BuildKit:
bash# Linux/Mac
export DOCKER_BUILDKIT=1
# Windows
$env:DOCKER_BUILDKIT=1
# Ou configure globalmente em ~/.docker/config.json
{
"features": {
"buildkit": true
}
}Multi-stage: O padrão fundamental
Exemplo básico: Node.js
dockerfile# Stage 1: Build
FROM node:18-alpine AS builder
WORKDIR /app
# Copia package files primeiro para cache de dependências
COPY package*.json ./
# Instala todas as dependências
RUN npm ci --only=production=false
# Copia código fonte
COPY . .
# Build da aplicação
RUN npm run build
# Stage 2: Produção
FROM node:18-alpine AS production
WORKDIR /app
# Cria usuário não-root para segurança
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001
# Copia apenas arquivos de produção do stage anterior
COPY --from=builder --chown=nodejs:nodejs /app/package*.json ./
COPY --from=builder --chown=nodejs:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=nodejs:nodejs /app/dist ./dist
# Troca para usuário não-root
USER nodejs
EXPOSE 3000
CMD ["node", "dist/index.js"]Resultado: imagem final sem devDependencies, sem código fonte TypeScript, sem ferramentas de build. Redução típica de 60-80%.
Exemplo: Go
dockerfile# Stage 1: Build
FROM golang:1.21-alpine AS builder
WORKDIR /app
# Instala dependências de build
RUN apk add --no-cache git ca-certificates
# Baixa dependências (cache separado de código)
COPY go.mod go.sum ./
RUN go mod download
# Copia código e build
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -ldflags="-w -s" -o main .
# Stage 2: Produção
FROM alpine:3.19
# Instala certificados SSL
RUN apk --no-cache add ca-certificates
WORKDIR /root/
# Copia apenas o binário compilado
COPY --from=builder /app/main .
# Usa ca-certificates do Alpine
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
EXPOSE 8080
CMD ["./main"]Resultado: binário Go estático com ~10-15MB, sem Go SDK, sem ferramentas.
Padrões avançados de otimização
Cache de dependências isolado
dockerfile# Isola instalação de dependências para cache granular
FROM node:18-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci
# Stage de build
FROM node:18-alpine AS builder
WORKDIR /app
# Copia dependências do stage deps
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
# Stage de produção
FROM node:18-alpine AS production
WORKDIR /app
# Copia apenas dependências de produção
COPY --from=deps /app/package*.json ./
RUN npm prune --production
COPY --from=builder /app/dist ./dist
CMD ["node", "dist/index.js"]Benefício: mudanças em código não invalidam cache de npm ci, acelerando builds subsequentes.
Arquitetura de multi-repositório
dockerfile# Stage de frontend
FROM node:18-alpine AS frontend-builder
WORKDIR /app
COPY frontend/package*.json ./frontend/
RUN cd frontend && npm ci
COPY frontend/ ./frontend/
RUN cd frontend && npm run build
# Stage de backend
FROM node:18-alpine AS backend-builder
WORKDIR /app
COPY backend/package*.json ./backend/
RUN cd backend && npm ci
COPY backend/ ./backend/
RUN cd backend && npm run build
# Stage de produção
FROM node:18-alpine
WORKDIR /app
# Copia artefatos de ambos os repositórios
COPY --from=frontend-builder /app/frontend/dist ./public
COPY --from=backend-builder /app/backend/dist ./dist
COPY --from=backend-builder /app/backend/package*.json ./
RUN npm prune --production
CMD ["node", "dist/index.js"]Técnicas de redução de tamanho
Imagens Alpine vs Slim vs Debian
dockerfile# Debian (maior)
FROM node:18
# Tamanho: ~900MB
# Debian Slim (balanceado)
FROM node:18-slim
# Tamanho: ~250MB
# Alpine (menor)
FROM node:18-alpine
# Tamanho: ~180MBTrade-off: Alpine usa musl libc em vez de glibc. A maioria das aplicações funciona, mas alguns pacotes nativos podem ter problemas. Use Alpine quando possível; Slim quando precisar de compatibilidade glibc.
Multi-stage com distroless
dockerfile# Build stage
FROM golang:1.21 AS builder
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -ldflags="-w -s" -o main .
# Produção com distroless (Google)
FROM gcr.io/distroless/base-debian12
COPY --from=builder /app/main .
EXPOSE 8080
CMD ["./main"]Benefício: distroless não tem shell, gerenciador de pacotes ou outras ferramentas. Superfície de ataque mínima. Tamanho: ~5-10MB.
Eliminação de arquivos desnecessários
dockerfile# No stage final, remove caches, logs e arquivos temporários
RUN npm cache clean --force && \
rm -rf /tmp/* /var/tmp/* && \
rm -rf /root/.npm /root/.cache && \
find /app -name "*.log" -delete && \
find /app -type d -name ".git" -prune -exec rm -rf {} +
# Para Alpine
RUN rm -rf /var/cache/apk/*BuildKit Features avançadas
Inline caching para CI/CD
bash# Primeiro build: cria cache local
docker build \
--build-arg BUILDKIT_INLINE_CACHE=1 \
--cache-from myregistry.io/myapp:buildcache \
-t myapp:latest \
.
# Push do cache
docker push myregistry.io/myapp:buildcache
# Builds subsequentes: usa cache remoto
docker build \
--build-arg BUILDKIT_INLINE_CACHE=1 \
--cache-from myregistry.io/myapp:buildcache \
--cache-to myregistry.io/myapp:buildcache \
-t myapp:latest \
.Benefício: builds de CI/CD podem aproveitar cache de builds anteriores, reduzindo tempo de build de minutos para segundos.
Build secrets sem vazamento
dockerfile# Sintaxe de secret do BuildKit
RUN --mount=type=secret,id=aws,dst=/root/.aws/credentials \
npm ci
# Para builds que precisam de access tokens privados
RUN --mount=type=secret,id=github_token,dst=/tmp/token \
npm config set //registry.npmjs.org/:_authToken $(cat /tmp/token)bash# Passa secret via CLI (não fica na imagem)
docker build \
--secret id=aws,src=$HOME/.aws/credentials \
--secret id=github_token,src=$GITHUB_TOKEN \
-t myapp:latest \
.Benefício: credenciais são acessíveis durante build mas nunca são incluídas na imagem final.
Build mounts para desempenho
dockerfile# Usa bind mount do host ao invés de COPY para desenvolvimento
FROM node:18-alpine AS development
WORKDIR /app
# Mount do código fonte (read-only)
RUN --mount=type=bind,source=.,target=/app,readonly \
npm run build
# Para watch mode em desenvolvimento
RUN --mount=type=bind,source=.,target=/app \
npm run devBenefício: durante desenvolvimento, código fonte é montado diretamente, evitando cópias e cache do Docker.
Boas práticas de segurança
Usuário não-root
dockerfileFROM node:18-alpine
WORKDIR /app
# Cria usuário e grupo
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001
# Copia arquivos com ownership correto
COPY --chown=nodejs:nodejs package*.json ./
RUN npm ci --only=production && \
npm cache clean --force
COPY --chown=nodejs:nodejs . .
# Troca para usuário não-root
USER nodejs
EXPOSE 3000
CMD ["node", "index.js"]Scan de vulnerabilidades no build
bash# Usa Trivy durante build
FROM node:18-alpine AS security-scan
# Instala Trivy
RUN apk add --no-cache curl
# Scan da imagem final
COPY --from=production /app /app
RUN apk add --no-cache trivy && \
trivy image --exit-code 1 --severity HIGH,CRITICAL myapp:latestMulti-stage com security scan
dockerfile# Stage de produção
FROM node:18-alpine AS production
# ... configuração de produção ...
# Stage de segurança
FROM aquasec/trivy:latest AS security-scan
COPY --from=production / /app
RUN trivy image --exit-code 0 --severity HIGH,CRITICAL /app
# Imagem final passa por scan
FROM productionMedindo impacto de otimização
Métricas antes/depois
bash# Ver tamanho de imagem
docker images myapp:latest --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}"
# Histórico de camadas
docker history myapp:latest
# Análise de conteúdo de camada
docker diff <container_id>
# Tamanho de artefatos
docker run --rm myapp:latest du -sh /app/node_modules
docker run --rm myapp:latest du -sh /app/distBenchmarks típicos
| Aplicação | Antes (monolítico) | Depois (multi-stage) | Redução |
|---|---|---|---|
| Node.js + TypeScript | 850MB | 180MB | 79% |
| Go application | 900MB | 12MB | 99% |
| Python (FastAPI) | 1.2GB | 350MB | 71% |
| React + Nginx | 700MB | 25MB | 96% |
Anti-padrões comuns
Anti-padrão 1: COPY no final do Dockerfile
dockerfile# RUIM: Copia tudo primeiro, invalida todo cache quando qualquer arquivo muda
FROM node:18-alpine
WORKDIR /app
COPY . .
RUN npm ciSolução:
dockerfile# BOM: Isola dependências para cache
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .Anti-padrão 2: Não remover cache de npm
dockerfile# RUIM: npm cache permanece na imagem
FROM node:18-alpine
WORKDIR /app
COPY . .
RUN npm installSolução:
dockerfile# BOM: Remove cache após install
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production && \
npm cache clean --forceAnti-padrão 3: DevDependencies em produção
dockerfile# RUIM: Instala tudo, incluindo devDependencies
FROM node:18-alpine
COPY package*.json ./
RUN npm installSolução:
dockerfile# BOM: Instala apenas dependências de produção
FROM node:18-alpine
COPY package*.json ./
RUN npm ci --only=productionIntegração com CI/CD
GitHub Actions
yamlname: Build and Push
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Cache Docker layers
uses: actions/cache@v3
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-
- name: Login to registry
uses: docker/login-action@v3
with:
registry: ${{ secrets.REGISTRY_URL }}
username: ${{ secrets.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ secrets.REGISTRY_URL }}/myapp:latest
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max
build-args: |
BUILDKIT_INLINE_CACHE=1Conclusão
Multi-stage builds com BuildKit transformam containers inchados em imagens de produção eficientes. A técnica é simples em conceito — separar ambiente de build de ambiente de runtime — mas exige disciplina para implementar consistentemente.
O impacto é mensurável: reduções de 60-99% no tamanho de imagem, deploys significativamente mais rápidos, menor custo de armazenamento e superfície de ataque reduzida. Para empresas executando centenas ou milhares de containers, essas economias se acumulam em números significativos.
BuildKit adiciona features como inline caching e build secrets que tornam o processo mais seguro e eficiente, especialmente em ambientes de CI/CD. Combinar multi-stage builds com imagens Alpine ou distroless cria imagens pequenas, seguras e rápidas de fazer deploy.
A melhor parte: a técnica é linguagem-agnóstica. Node.js, Go, Python, Java, Ruby — todas se beneficiam de isolar build de produção. O próximo passo não é apenas implementar multi-stage builds, mas estabelecer padrões organizacionais: templates de Dockerfile, linhas de base de segurança e métricas de otimização contínua.
Seus containers estão inchados e lentos de fazer deploy? Fale com especialistas DevOps da Imperialis para otimizar sua estratégia de containers, reduzir custos e acelerar deploys.