← Voltar para o blog

Go e Docker: Imagens Mínimas com Multi-stage

Aprenda a criar imagens Docker mínimas para Go com multi-stage builds. Compare scratch, distroless e Alpine, e aplique boas práticas de segurança em produção.

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:

ImagemTamanhoShellCertificados TLSDebugSegurança
scratch0 MBNãoNão (precisa copiar)ImpossívelMáxima
gcr.io/distroless/static~2 MBNãoSimLimitadoMuito alta
alpine~7 MBSimSimPossívelAlta

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.