Go com Docker: Guia Completo 2026
Go e Docker formam uma das duplas mais poderosas do desenvolvimento moderno. Enquanto Go produz binários estáticos e compactos, Docker oferece portabilidade e consistência. O resultado? Imagens de produção com menos de 10MB que rodam em qualquer lugar.
Neste guia, vamos do básico ao avançado: desde o primeiro Dockerfile até um setup de produção completo com Docker Compose, health checks e boas práticas.
Por que Go + Docker Funciona Tão Bem?
Antes de colocar a mão na massa, vale entender por que essa combinação é tão popular:
- Binários estáticos: Go compila tudo em um único executável, sem dependências externas
- Compilação cruzada nativa: compile para Linux mesmo estando no macOS ou Windows
- Imagens minúsculas: com
scratchou Alpine, suas imagens ficam entre 5-15MB - Inicialização instantânea: sem JVM, sem runtime, sem warm-up
- Baixo consumo de memória: ideal para containers com limites de recursos
Compare com outras linguagens:
| Stack | Tamanho típico da imagem |
|---|---|
| Go + scratch | ~5-10MB |
| Go + Alpine | ~15-20MB |
| Node.js + Alpine | ~150-200MB |
| Java + JRE | ~300-500MB |
| Python + pip | ~200-400MB |
Estrutura do Projeto
Vamos usar este projeto de exemplo ao longo do tutorial:
minha-api/
├── cmd/
│ └── server/
│ └── main.go
├── internal/
│ └── handler/
│ └── handler.go
├── go.mod
├── go.sum
├── Dockerfile
├── .dockerignore
└── docker-compose.yml
Aqui está nosso código Go inicial:
// cmd/server/main.go
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"time"
)
// Resposta padrão da API
type Resposta struct {
Mensagem string `json:"mensagem"`
Timestamp string `json:"timestamp"`
Versao string `json:"versao"`
}
func main() {
// Porta configurável via variável de ambiente
porta := os.Getenv("PORT")
if porta == "" {
porta = "8080"
}
// Versão da aplicação (injetada no build)
versao := os.Getenv("APP_VERSION")
if versao == "" {
versao = "dev"
}
mux := http.NewServeMux()
// Endpoint principal
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
resp := Resposta{
Mensagem: "Olá do Go + Docker!",
Timestamp: time.Now().Format(time.RFC3339),
Versao: versao,
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(resp)
})
// Health check para o Docker
mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
fmt.Fprintln(w, `{"status":"healthy"}`)
})
servidor := &http.Server{
Addr: ":" + porta,
Handler: mux,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 30 * time.Second,
}
log.Printf("Servidor iniciando na porta %s (versão %s)", porta, versao)
if err := servidor.ListenAndServe(); err != nil {
log.Fatal("Erro ao iniciar servidor:", err)
}
}
Dockerfile Básico
Começamos com um Dockerfile simples para entender os fundamentos:
# Dockerfile básico (NÃO use em produção)
FROM golang:1.22
WORKDIR /app
# Copia o código fonte
COPY . .
# Baixa dependências
RUN go mod download
# Compila a aplicação
RUN go build -o server ./cmd/server/
# Expõe a porta
EXPOSE 8080
# Executa o binário
CMD ["./server"]
Problema: essa imagem terá ~800MB porque inclui todo o SDK do Go, ferramentas de compilação, código fonte e cache. Ninguém quer isso em produção.
Construa e teste:
docker build -t minha-api:basico .
docker run -p 8080:8080 minha-api:basico
# Teste: curl http://localhost:8080
Multi-Stage Build: A Forma Certa
Multi-stage builds são o padrão ouro para Go com Docker. Usamos um estágio para compilar e outro, mínimo, para executar:
# ===========================================
# Estágio 1: Compilação
# ===========================================
FROM golang:1.22-alpine AS builder
# Instala certificados CA e timezone data
RUN apk add --no-cache ca-certificates tzdata
WORKDIR /build
# Copia apenas os arquivos de dependência primeiro (cache de camadas)
COPY go.mod go.sum ./
RUN go mod download && go mod verify
# Copia o restante do código
COPY . .
# Compila o binário estático
# CGO_ENABLED=0 garante compilação 100% estática
# -ldflags para reduzir tamanho do binário
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
-ldflags='-w -s -extldflags "-static"' \
-o server ./cmd/server/
# ===========================================
# Estágio 2: Produção (imagem mínima)
# ===========================================
FROM scratch
# Copia certificados CA (necessário para HTTPS)
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
# Copia dados de timezone (necessário para time.LoadLocation)
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
# Copia o binário compilado
COPY --from=builder /build/server /server
# Porta da aplicação
EXPOSE 8080
# Usuário não-root (segurança)
USER 65534:65534
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD ["/server", "-health"] || exit 1
# Executa o binário
ENTRYPOINT ["/server"]
Resultado: imagem de ~8MB contra ~800MB do Dockerfile básico.
# Construa a imagem otimizada
docker build -t minha-api:prod .
# Verifique o tamanho
docker images minha-api
# REPOSITORY TAG SIZE
# minha-api prod 8.2MB
# minha-api basico 812MB
Entendendo Cada Flag
| Flag | Para que serve |
|---|---|
CGO_ENABLED=0 | Desabilita CGo, garante binário 100% estático |
GOOS=linux | Compila para Linux (necessário se você está no macOS/Windows) |
-ldflags='-w -s' | Remove tabela de símbolos e info de debug (reduz ~30% do tamanho) |
scratch | Imagem base vazia, sem sistema operacional |
.dockerignore: Não Copie Lixo
O arquivo .dockerignore é tão importante quanto o .gitignore. Sem ele, você envia arquivos desnecessários para o contexto de build:
# Controle de versão
.git
.gitignore
# IDE e editor
.vscode/
.idea/
*.swp
*.swo
# Binários e builds locais
/server
*.exe
*.test
# Docker
Dockerfile
docker-compose.yml
.dockerignore
# Documentação
README.md
docs/
# Testes e CI
.github/
coverage.out
*.prof
# Variáveis de ambiente locais
.env
.env.local
Docker Compose: Go + PostgreSQL
Para desenvolvimento, Docker Compose orquestra múltiplos serviços. Aqui está um setup completo com Go + PostgreSQL + Redis:
# docker-compose.yml
version: "3.9"
services:
# ========================================
# Aplicação Go
# ========================================
api:
build:
context: .
dockerfile: Dockerfile
ports:
- "8080:8080"
environment:
- PORT=8080
- APP_VERSION=1.0.0
- DATABASE_URL=postgres://usuario:senha123@postgres:5432/minha_api?sslmode=disable
- REDIS_URL=redis://redis:6379/0
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_started
restart: unless-stopped
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
# ========================================
# PostgreSQL
# ========================================
postgres:
image: postgres:16-alpine
ports:
- "5432:5432"
environment:
POSTGRES_USER: usuario
POSTGRES_PASSWORD: senha123
POSTGRES_DB: minha_api
volumes:
- postgres_dados:/var/lib/postgresql/data
- ./scripts/init.sql:/docker-entrypoint-initdb.d/init.sql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U usuario -d minha_api"]
interval: 10s
timeout: 5s
retries: 5
# ========================================
# Redis (cache e sessões)
# ========================================
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis_dados:/data
volumes:
postgres_dados:
redis_dados:
Dockerfile para Desenvolvimento com Hot Reload
Para desenvolvimento local, use o Air para hot reload:
# Dockerfile.dev — para desenvolvimento local
FROM golang:1.22-alpine
# Instala o Air para hot reload
RUN go install github.com/air-verse/air@latest
WORKDIR /app
# Copia dependências
COPY go.mod go.sum ./
RUN go mod download
# Copia código fonte
COPY . .
# Porta da aplicação
EXPOSE 8080
# Inicia com hot reload
CMD ["air", "-c", ".air.toml"]
No docker-compose.yml, troque o build para desenvolvimento:
# docker-compose.override.yml (carregado automaticamente)
services:
api:
build:
dockerfile: Dockerfile.dev
volumes:
- .:/app # Monta o código local
- go_modules:/go # Cache dos módulos Go
volumes:
go_modules:
Comandos úteis:
# Subir tudo
docker compose up -d
# Ver logs da API
docker compose logs -f api
# Reconstruir após mudança no Dockerfile
docker compose up -d --build
# Parar tudo e remover volumes
docker compose down -v
Variáveis de Ambiente e Configuração
Go lê variáveis de ambiente nativamente, mas uma abordagem estruturada é melhor:
// internal/config/config.go
package config
import (
"fmt"
"os"
"strconv"
"time"
)
// Config armazena toda a configuração da aplicação
type Config struct {
Port string
DatabaseURL string
RedisURL string
JWTSecret string
AppVersion string
ReadTimeout time.Duration
WriteTimeout time.Duration
}
// Carrega lê a configuração das variáveis de ambiente
func Carrega() (*Config, error) {
cfg := &Config{
Port: getEnv("PORT", "8080"),
DatabaseURL: getEnv("DATABASE_URL", ""),
RedisURL: getEnv("REDIS_URL", ""),
JWTSecret: getEnv("JWT_SECRET", ""),
AppVersion: getEnv("APP_VERSION", "dev"),
ReadTimeout: getDurationEnv("READ_TIMEOUT", 10*time.Second),
WriteTimeout: getDurationEnv("WRITE_TIMEOUT", 10*time.Second),
}
// Validações obrigatórias
if cfg.DatabaseURL == "" {
return nil, fmt.Errorf("DATABASE_URL é obrigatória")
}
return cfg, nil
}
// getEnv retorna a variável de ambiente ou um valor padrão
func getEnv(chave, padrao string) string {
if valor, existe := os.LookupEnv(chave); existe {
return valor
}
return padrao
}
// getDurationEnv lê uma duração em segundos da env
func getDurationEnv(chave string, padrao time.Duration) time.Duration {
if valor, existe := os.LookupEnv(chave); existe {
if segundos, err := strconv.Atoi(valor); err == nil {
return time.Duration(segundos) * time.Second
}
}
return padrao
}
Gotchas Comuns: Armadilhas do Go com Docker
1. CGO_ENABLED=0 e a Imagem scratch
Se você esquecer CGO_ENABLED=0, o binário vai depender de bibliotecas C do sistema:
# Erro comum: binário não executa no scratch
standard_init_linux.go: exec user process caused: no such file or directory
Solução: sempre use CGO_ENABLED=0 quando o destino for scratch.
2. Timezone Data Ausente
Sem os dados de timezone, time.LoadLocation("America/Sao_Paulo") retorna erro:
// Isso FALHA no scratch sem timezone data
loc, err := time.LoadLocation("America/Sao_Paulo")
if err != nil {
log.Fatal("timezone não encontrado:", err)
}
Solução: copie os dados de timezone no Dockerfile (já fizemos acima) ou importe o pacote time/tzdata:
import _ "time/tzdata" // embute os dados de timezone no binário
3. Certificados CA para HTTPS
Sem certificados CA, requisições HTTPS externas falham:
// Isso FALHA no scratch sem certificados CA
resp, err := http.Get("https://api.externa.com/dados")
// x509: certificate signed by unknown authority
Solução: copie os certificados do estágio de build (já fizemos acima).
4. DNS e net Package
O pacote net do Go usa CGo por padrão para resolução DNS. Com CGO_ENABLED=0, usa o resolver puro Go:
// Force o resolver Go puro (recomendado para containers)
// Adicione no início do main():
import "net"
func init() {
// Usa o resolver DNS puro Go
net.DefaultResolver.PreferGo = true
}
Build Otimizado para Produção
Aqui está o Dockerfile final com todas as boas práticas reunidas:
# =============================================
# Dockerfile de Produção — Go + Docker
# =============================================
# Argumentos de build
ARG GO_VERSION=1.22
ARG APP_VERSION=dev
# Estágio 1: Dependências (cache eficiente)
FROM golang:${GO_VERSION}-alpine AS deps
WORKDIR /build
COPY go.mod go.sum ./
RUN go mod download && go mod verify
# Estágio 2: Compilação
FROM golang:${GO_VERSION}-alpine AS builder
RUN apk add --no-cache ca-certificates tzdata
WORKDIR /build
COPY --from=deps /go/pkg /go/pkg
COPY . .
ARG APP_VERSION
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
-ldflags="-w -s -X main.version=${APP_VERSION}" \
-o /app/server ./cmd/server/
# Estágio 3: Produção
FROM scratch
# Metadados da imagem
LABEL maintainer="seuprojeto@email.com"
LABEL version="${APP_VERSION}"
# Arquivos essenciais do sistema
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
# Binário da aplicação
COPY --from=builder /app/server /server
# Porta
EXPOSE 8080
# Segurança: usuário não-root
USER 65534:65534
# Ponto de entrada
ENTRYPOINT ["/server"]
Injetando Versão no Build
No seu main.go, declare a variável que receberá a versão:
// main.go
package main
// Injetado via ldflags durante o build
var version = "dev"
func main() {
log.Printf("Iniciando servidor v%s", version)
// ...
}
Build com versão:
docker build \
--build-arg APP_VERSION=1.2.3 \
-t minha-api:1.2.3 .
Comandos Docker Essenciais
# Construir imagem
docker build -t minha-api:latest .
# Rodar container
docker run -d -p 8080:8080 --name api minha-api:latest
# Ver logs
docker logs -f api
# Entrar no container (se usar alpine)
docker exec -it api sh
# Inspecionar imagem (ver camadas)
docker history minha-api:latest
# Verificar health check
docker inspect --format='{{.State.Health.Status}}' api
# Remover imagens não utilizadas
docker image prune -a
# Tag e push para registry
docker tag minha-api:latest registry.exemplo.com/minha-api:1.0.0
docker push registry.exemplo.com/minha-api:1.0.0
Conclusão
Go e Docker são uma combinação natural. A capacidade do Go de produzir binários estáticos e auto-contidos permite criar imagens Docker extremamente pequenas e seguras. Recapitulando os pontos principais:
- Sempre use multi-stage builds para separar compilação e execução
- Use
scratchou Alpine como imagem base de produção CGO_ENABLED=0é obrigatório para imagens scratch- Copie certificados CA e timezone data quando usar scratch
- Docker Compose simplifica o desenvolvimento local com múltiplos serviços
- Health checks garantem que o Docker saiba se sua aplicação está saudável
.dockerignoreevita enviar arquivos desnecessários no build
Com essas práticas, suas imagens Go ficarão com menos de 10MB, iniciarão em milissegundos e estarão prontas para produção em qualquer ambiente que suporte containers.
Veja também
- API REST com Go — Crie a API para containerizar
- Go com PostgreSQL — Docker Compose com banco
- Microservices com Go — Arquitetura distribuída
- Vagas DevOps Go — Vagas que pedem Go + Docker