Go é uma das linguagens mais adequadas para containers. Binários compilados estaticamente, sem dependências de runtime, e tamanho final que pode chegar a menos de 10 MB. Mas para aproveitar isso, você precisa saber construir suas imagens Docker corretamente — e é aí que entram os multi-stage builds.
Neste guia, vamos desde um Dockerfile básico até uma imagem de produção otimizada, passando por comparações entre imagens base, boas práticas de segurança e exemplos prontos para copiar.
Por que Go É Ideal para Containers
Diferente de linguagens como Python ou Node.js, Go compila para um binário estático — um único arquivo executável sem dependências externas. Isso significa que sua imagem Docker final não precisa de interpretador, runtime, ou bibliotecas do sistema:
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "API rodando em Go!")
})
http.ListenAndServe(":8080", nil)
}
Esse servidor HTTP simples pode rodar em uma imagem Docker de menos de 10 MB. Compare com uma aplicação Node.js equivalente que facilmente ultrapassa 200 MB.
Dockerfile Básico vs Multi-stage
Abordagem Básica (NÃO recomendada)
FROM golang:1.24
WORKDIR /app
COPY . .
RUN go build -o server .
CMD ["./server"]
Resultado: imagem de ~1.2 GB. Inclui o compilador Go, ferramentas de build, código fonte — nada disso é necessário em produção.
Multi-stage Build (Recomendado)
# Etapa 1: Build
FROM golang:1.24 AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o server .
# Etapa 2: Imagem final
FROM scratch
COPY --from=builder /app/server /server
EXPOSE 8080
ENTRYPOINT ["/server"]
Resultado: imagem de ~6 MB. Só contém o binário compilado — zero overhead.
A flag CGO_ENABLED=0 garante compilação estática (sem depender de libc). As flags -ldflags="-s -w" removem tabela de símbolos e informações de debug, reduzindo o binário em ~30%.
Scratch vs Distroless vs Alpine
A escolha da imagem base final é uma das decisões mais importantes. Cada opção tem trade-offs:
| Imagem | Tamanho | Shell | Certificados TLS | Debug | Segurança |
|---|---|---|---|---|---|
scratch | 0 MB | Não | Não (precisa copiar) | Impossível | Máxima |
gcr.io/distroless/static | ~2 MB | Não | Sim | Limitado | Muito alta |
alpine | ~7 MB | Sim | Sim | Possível | Alta |
Quando usar cada uma
- scratch: APIs internas sem requisições HTTPS externas. Segurança máxima.
- distroless: APIs que fazem chamadas HTTPS (já inclui certificados CA). Melhor equilíbrio.
- alpine: Quando você precisa de shell para debug em produção ou depende de ferramentas do sistema.
Para a maioria dos projetos Go, distroless é a melhor escolha:
FROM golang:1.24 AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o server .
FROM gcr.io/distroless/static-debian12
COPY --from=builder /app/server /server
EXPOSE 8080
ENTRYPOINT ["/server"]
Se você precisa de scratch com suporte HTTPS, copie os certificados manualmente:
FROM scratch
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /app/server /server
ENTRYPOINT ["/server"]
Boas Práticas de Segurança
Usuário não-root
Nunca rode containers como root em produção. Com distroless, use o usuário nonroot embutido:
FROM gcr.io/distroless/static-debian12
COPY --from=builder /app/server /server
USER nonroot:nonroot
EXPOSE 8080
ENTRYPOINT ["/server"]
Arquivo .dockerignore
Evite copiar arquivos desnecessários para o contexto de build:
.git
.gitignore
README.md
docs/
*.md
.env
.env.*
tmp/
vendor/
Scan de vulnerabilidades
Integre scanning na sua pipeline de CI com ferramentas como Trivy:
docker build -t minha-api:latest .
trivy image minha-api:latest
Imagens baseadas em scratch ou distroless quase nunca apresentam CVEs porque não incluem pacotes do sistema operacional.
Otimizando o Build Cache
A ordem das instruções no Dockerfile impacta diretamente o tempo de build. Copie go.mod e go.sum antes do código fonte:
FROM golang:1.24 AS builder
WORKDIR /app
# Camada de dependências (muda raramente = cache eficiente)
COPY go.mod go.sum ./
RUN go mod download
# Camada de código (muda sempre)
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o server .
Quando você altera apenas o código Go, o Docker reutiliza a camada de go mod download — economizando minutos em cada build.
Docker Compose para Desenvolvimento
Em desenvolvimento local, use Docker Compose para orquestrar sua API Go com bancos de dados e outros serviços:
services:
api:
build: .
ports:
- "8080:8080"
environment:
- DATABASE_URL=postgres://user:pass@db:5432/app?sslmode=disable
depends_on:
db:
condition: service_healthy
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: app
ports:
- "5432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user -d app"]
interval: 5s
timeout: 3s
retries: 5
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
Para mais detalhes sobre Go com PostgreSQL, veja nosso tutorial completo de CRUD e o guia de sqlc para queries type-safe.
Dockerfile de Produção Completo
Reunindo todas as boas práticas em um template pronto para usar:
# syntax=docker/dockerfile:1
# ---- Build ----
FROM golang:1.24 AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download && go mod verify
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
go build -ldflags="-s -w" -trimpath -o server .
# ---- Final ----
FROM gcr.io/distroless/static-debian12
COPY --from=builder /app/server /server
USER nonroot:nonroot
EXPOSE 8080
ENTRYPOINT ["/server"]
As flags adicionais:
go mod verify— valida integridade das dependências-trimpath— remove caminhos locais do binário (segurança)GOARCH=amd64— explicita a arquitetura (previsibilidade)
Integração com Kubernetes e Observabilidade
Imagens Docker mínimas são fundamentais para deploy em Kubernetes. Menos camadas significam pulls mais rápidos, menos superfície de ataque, e deploys mais ágeis.
Para monitorar sua aplicação Go em containers, configure OpenTelemetry para tracing distribuído e métricas. E para testes de integração com containers em Go, o Testcontainers permite criar bancos de dados e serviços efêmeros diretamente nos seus testes.
Se seu serviço usa gRPC ou mensageria com Kafka, as mesmas práticas de multi-stage build se aplicam — o binário Go final é sempre autocontido.
Para logging estruturado dentro de containers, configure o slog para saída JSON compatível com coletores como Fluentd e Loki. E para gerenciar secrets em runtime, nunca embuta credenciais na imagem — use variáveis de ambiente ou ferramentas como Vault.
Quem está migrando de outras linguagens pode comparar a experiência de containerização: Go se destaca frente a Python e Kotlin pelo binário estático sem runtime, e rivais como Rust oferecem imagens igualmente mínimas com trade-offs diferentes em tempo de compilação.
FAQ
Qual a menor imagem Docker possível para Go?
Usando scratch como base e compilação estática com CGO_ENABLED=0 e -ldflags="-s -w", é possível criar imagens de 5-10 MB para APIs simples. O tamanho varia conforme as dependências do projeto.
Preciso de Alpine se uso Go com multi-stage build?
Na maioria dos casos, não. Distroless ou scratch são preferíveis para produção. Use Alpine apenas se precisar de shell para debug ou se suas dependências requerem bibliotecas C do sistema.
Como faço multi-stage build com CGO habilitado?
Quando CGO é necessário (ex.: SQLite, bindings C), use golang:1.24-alpine como builder com apk add gcc musl-dev, e alpine como imagem final. O binário não será estático, então scratch não funciona nesse caso.
Docker Compose serve para produção?
Docker Compose é ideal para desenvolvimento local e CI. Para produção, use Kubernetes ou plataformas como Cloud Run, ECS ou Fly.io que oferecem auto-scaling, health checks e rolling deploys.
Como otimizar o tempo de build do Docker com Go modules?
Copie go.mod e go.sum antes do código fonte e execute go mod download em uma camada separada. Isso cria uma camada de cache que só é invalidada quando as dependências mudam, economizando minutos em cada build.