Docker: Construção Multi-estágio
Última atualização: 2026-08-26
Uma imagem encolhe de 1,2 GB para 12 MB — builds multi-estágio são a técnica mais eficaz para reduzir o tamanho da imagem.
1. O Que Você Vai Aprender
- Princípios e Vantagens da Construção Multi-estágio
- Copiar artefatos da fase de build
- Selecionar a imagem base ideal para execução
- Estratégias para Reduzir o Tamanho da Imagem
- Otimização de Cache e Técnicas de Depuração
2. Uma História Real de uma Pessoa Desenvolvedora Go
(1) Ponto de Dor: Uma imagem de aplicação de 1,2 GB
A imagem da aplicação Go de Alice tem 1,2 GB — porque ela usou golang:1.22 como imagem base, que inclui o compilador Go completo, código fonte e toolchain. Mas o ambiente de produção precisa apenas de um binário compilado de 12 MB. Cada implantação leva 5 minutos para baixar a imagem, e o servidor na nuvem com 50 MB de banda fica lento a cada lançamento.
(2) Uma Abordagem de Construção Multi-estágio
Bob a ensinou a usar builds multi-estágio: "No primeiro estágio, use golang:1.22 para compilar; no segundo estágio, copie a saída compilada do Alpine."
# Estágio 1: Build
FROM golang:1.22 AS builder
WORKDIR /src
COPY . .
RUN go build -o server
# Estágio 2: Execução (apenas o binário, sem o compilador Go)
FROM alpine:3.19
COPY --from=builder /src/server /usr/local/bin/
CMD ["server"]
(3) Benefício: O tamanho da imagem foi reduzido de 1,2 GB para 12 MB
O tamanho da imagem foi reduzido em 100 vezes, o tempo de download e implantação caiu de 5 minutos para 5 segundos, e as vulnerabilidades de segurança foram reduzidas de mais de 200 para 10 (o compilador não está incluído na imagem de produção).
3. Princípios da Construção Multi-estágio
(1) Comparação Entre Estágio Único e Multi-estágio
graph TB
subgraph Single["Build de Estágio Único"]
S1["golang:1.22<br/>Base 800 MB"] --> S2["go build<br/>Saída da Compilação"] --> S3["Imagem Final<br/>1,2 GB<br/>(Inclui compilador + código fonte)"]
end
subgraph Multi["Build Multi-estágio"]
M1["Estágio 1: golang:1.22<br/>800 MB (descartado)"] -->|"COPY --from"| M3["Estágio 2: alpine:3.19<br/>Base 7 MB"]
M1 --> M2["go build<br/>Saída da Compilação"]
M2 -->|"Copiar apenas o binário"| M3
M3 --> M4["Imagem Final<br/>12 MB<br/>(Apenas o Binário)"]
end
(1) Mecanismo Central
| Estágio | Propósito | Conteúdo | Imagem Final |
|---|---|---|---|
| Estágio Builder | Compilação/Build | Compilador, código fonte, ferramentas de build | ❌ Descartado |
| Estágio Runner | Execução | Copiar apenas artefatos necessários | ✅ Retido |
4. Prática de Builds Multi-estágio
▶ Exemplo: Builds multi-estágio em Go (Dificuldade: ⭐⭐)
# ============================================
# Estágio 1: Compilar o binário Go
# ============================================
FROM golang:1.22 AS builder
WORKDIR /src
# Copiar arquivos de dependência primeiro (otimização de cache)
COPY go.mod go.sum ./
RUN go mod download
# Copiar código fonte e compilar
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o server
# ============================================
# Estágio 2: Criar imagem de runtime mínima
# ============================================
FROM alpine:3.19
# Instalar certificados CA (para requisições HTTPS)
RUN apk --no-cache add ca-certificates
# Copiar apenas o binário compilado do builder
COPY --from=builder /src/server /usr/local/bin/server
# Executar como usuário não-root
RUN adduser -D -u 1000 appuser
USER appuser
EXPOSE 8080
CMD ["server"]
# Construir a imagem multi-estágio
docker build -t go-app:1.0 .
# Verificar o tamanho da imagem final
docker images go-app:1.0
REPOSITORY TAG IMAGE ID SIZE
go-app 1.0 a1b2c3d4e5f6 12.5MB
▶ Exemplo: Build com Node.js → Executar com Nginx (Dificuldade: ⭐⭐⭐)
Projetos front-end como React e Vue: Arquivos estáticos são gerados durante a fase de build, e o Nginx é usado para servir requisições HTTP durante o runtime.
# ============================================
# Estágio 1: Compilar aplicação React
# ============================================
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# ============================================
# Estágio 2: Servir com Nginx
# ============================================
FROM nginx:1.25-alpine
# Copiar ativos compilados do estágio builder
COPY --from=builder /app/build /usr/share/nginx/html
# Configuração personalizada do Nginx para roteamento SPA
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
node_modules pode ter 500+ MB, mas a saída do build (HTML/CSS/JS) geralmente tem apenas 5–20 MB.
▶ Exemplo: Clonando um venv Python (Dificuldade: ⭐⭐)
Projetos Python também podem ser construídos em múltiplos estágios: o primeiro estágio cria um ambiente virtual, e o segundo estágio simplesmente copia o ambiente virtual e o código da aplicação.
# Estágio 1: Criar ambiente virtual
FROM python:3.12 AS builder
WORKDIR /app
COPY requirements.txt .
RUN python -m venv /opt/venv && \
/opt/venv/bin/pip install --no-cache-dir -r requirements.txt
# Estágio 2: Executar apenas com o venv
FROM python:3.12-slim
WORKDIR /app
COPY --from=builder /opt/venv /opt/venv
COPY . .
ENV PATH="/opt/venv/bin:$PATH"
CMD ["python", "app.py"]
5. Selecionando uma Imagem Base para Execução
(1) Comparação das Imagens de Três Bases Mínimas
| Imagem Base | Tamanho | Inclui Shell | Inclui Gerenciador de Pacotes | Inclui libc | Casos de Uso |
|---|---|---|---|---|---|
scratch |
0 MB | ❌ | ❌ | ❌ | Binários Go/Rust compilados estaticamente |
distroless |
2–20 MB | ❌ | ❌ | ✅ (glibc) | Do Google, extremamente seguro |
alpine |
7 MB | ✅ (ash) | ✅ (apk) | ✅ (musl) | Quando a depuração é necessária |
(2) Observações sobre a Imagem Scratch
# Scratch: absolutamente nada na imagem
FROM scratch
COPY --from=builder /src/server /server
CMD ["/server"]
scratch não inclui shell, ca-certificates ou dados de fuso horário. Programas Go devem ser compilados estaticamente CGO_ENABLED=0 para executar em scratch. Se seu programa requer requisições HTTPS ou logs com timestamp, é mais seguro usar alpine.
6. Técnicas Avançadas para Builds Multi-estágio
▶ Exemplo: Nomeação de Estágios e --target para Seleção de Build (Dificuldade: ⭐⭐)
# Estágios nomeados para clareza
FROM golang:1.22 AS builder
WORKDIR /src
COPY . .
RUN go build -o server
# Alternativa: estágio de desenvolvimento com ferramentas de hot-reload
FROM golang:1.22 AS dev
WORKDIR /src
RUN go install github.com/cosmtrek/air@latest
COPY . .
CMD ["air"]
# Produção: runtime mínimo
FROM alpine:3.19 AS prod
COPY --from=builder /src/server /usr/local/bin/
CMD ["server"]
# Construir apenas o estágio dev
docker build --target dev -t myapp:dev .
# Construir apenas o estágio prod
docker build --target prod -t myapp:prod .
▶ Exemplo: --target para depurar artefatos intermediários (Dificuldade: ⭐⭐⭐)
# Construir e inspecionar o estágio builder
docker build --target builder -t myapp:builder .
# Executar a imagem builder para depurar problemas de build
docker run -it myapp:builder bash
# Verificar o binário compilado
docker run --rm myapp:builder ls -la /src/server
7. Referência de Tamanhos de Saída de Compilação por Linguagem
| Linguagem | Imagem Base | Saída do Build | Imagem Final | Taxa de Redução |
|---|---|---|---|---|
| Go | golang:1.22 (780 MB) | Binário estático 12 MB | alpine: 12 MB | 65x |
| Rust | rust:1.75 (1,5 GB) | Binário estático 8 MB | alpine: 15 MB | 100x |
| Node.js | node:20 (1,1 GB) | build/ 5-20 MB | nginx-alpine: 25 MB | 44x |
| Java | eclipse-temurin:21 (450 MB) | JAR 30 MB | jre-alpine: 170 MB | 2,6x |
| Python | python:3.12 (1 GB) | venv/ 50 MB | slim: 200 MB | 5x |
8. Exemplo Completo: Build Multi-estágio para uma Aplicação Web Go
# ============================================
# Dockerfile multi-estágio para aplicação web Go
# Estágio 1: Build com cache de dependências
# Estágio 2: Runtime Alpine mínimo
# ============================================
# ---------- Estágio 1: Builder ----------
FROM golang:1.22-alpine AS builder
# Instalar git (para go mod download com repositórios privados)
RUN apk add --no-cache git
WORKDIR /src
# Cache: copiar arquivos de dependência primeiro
COPY go.mod go.sum ./
RUN go mod download
# Copiar código fonte
COPY . .
# Compilar binário estático (CGO_ENABLED=0 para scratch/alpine)
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
go build -ldflags="-w -s" -o /app/server
# ---------- Estágio 2: Runtime ----------
FROM alpine:3.19
# Instalar dependências de runtime
RUN apk --no-cache add ca-certificates tzdata
# Criar usuário não-root
RUN adduser -D -u 1000 appuser
WORKDIR /app
# Copiar apenas o binário do builder
COPY --from=builder /app/server .
# Definir propriedade
RUN chown -R appuser:appuser /app
USER appuser
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
CMD wget -qO- http://localhost:8080/health || exit 1
CMD ["/app/server"]
# Construir
docker build -t go-web:1.0 .
# Executar
docker run -d -p 8080:8080 --name go-web go-web:1.0
# Verificar tamanho e saúde
docker images go-web:1.0
docker inspect go-web --format='Size: {{.Size}}, Health: {{.State.Health.Status}}'
REPOSITORY TAG SIZE
go-web 1.0 15.2MB
Size: 15974400, Health: healthy
❓ Perguntas Frequentes
P: A construção multi-estágio afeta a velocidade de build? R: Não tem um impacto significativo. As camadas do estágio Builder ainda são armazenadas em cache — se o código não mudou,
go buildusa o cache diretamente. Apenas a imagem final não inclui as camadas do Builder. A velocidade de build é quase a mesma de um build de estágio único, mas o tamanho da imagem é significativamente reduzido.
P: Posso usar builds multi-estágio para depuração? R: Sim. Use
--target builderpara construir até o estágio Builder, depois usedocker run -it myapp:builder bashpara entrar no estágio de depuração. Você também pode definir um estágiodevno Dockerfile que inclua ferramentas de depuração (como dlv ou air).
P: Como depurar sem um shell na imagem scratch? R: Como a imagem scratch não tem shell, você não pode usar
docker execpara entrar nela. Métodos de depuração: ① Construir uma imagem de depuração usando--target builder; ② Alterar temporariamenteFROM scratchparaFROM alpinepara depuração; ③ Usardocker cpno host para copiar arquivos de log para fora.
P: Segredos podem ser passados através de builds multi-estágio? R: Não inclua segredos no estágio Builder — embora a camada Builder não seja incluída na imagem final,
docker historyainda é visível. Use--mount=type=secret(um recurso do BuildKit) para passar segredos de forma segura:RUN --mount=type=secret,id=ssh_key cp /run/secrets/ssh_key /root/.ssh/id_rsa.
P: A imagem distroless é segura? R: É muito segura. A imagem distroless mantida pelo Google não inclui shell, gerenciador de pacotes ou qualquer ferramenta não essencial, resultando em uma superfície de ataque extremamente pequena. No entanto, a depuração é difícil (pois não há shell), então recomendamos usar Alpine para desenvolvimento e distroless para produção (com depuração externa via logs e monitoramento).
P: Por que imagens Go usam Alpine em vez de Scratch? R: Alpine fornece ca-certificates (necessário para requisições HTTPS) e dados de fuso horário (necessário para timestamps de log), além de um shell para depuração de emergência. Os 5 MB extras valem muito a pena. Scratch é usado apenas em cenários que exigem otimização extrema (como dispositivos embarcados).
📖 Resumo
- Build multi-estágio: A compilação ocorre durante o estágio Builder, enquanto o estágio Runner apenas copia os artefatos, reduzindo o tamanho da imagem de gigabytes para megabytes
FROM xxx AS builderNomeia a fase,COPY --from=builderCopia entre fases- Opções de imagem base: scratch (0 MB) > distroless (2–20 MB) > alpine (7 MB)
--targetPode ser construído em um estágio especificado para depuração e desenvolvimento- Linguagens compiladas como Go e Rust se beneficiam mais (redução de 100x no tamanho), seguidas por projetos front-end Node.js (44x)
CGO_ENABLED=0+-ldflags="-w -s"são os parâmetros padrão para compilação estática em Go
📝 Exercícios
- Exercício Básico (Dificuldade: ⭐): Compare os tamanhos das imagens de aplicação Go construídas usando build de estágio único e multi-estágio — primeiro use
FROM golang:1.22para um build de estágio único, depois use um build multi-estágio, e registre a diferença de tamanho entre as duas imagens. - Exercício Avançado (Dificuldade: ⭐⭐): Construa uma aplicação React em múltiplos estágios:
node:20-alpineBuild →nginx:1.25-alpineExecutar. Compare os tamanhos finais da imagem base Node.js e da imagem base Nginx. - Desafio (Dificuldade: ⭐⭐⭐): Use
docker historypara analisar o tamanho de cada camada em uma imagem construída com multi-estágio e explique por que as camadas do estágio Builder não estão incluídas na imagem final.