Go e Docker são uma combinação poderosa. Enquanto aplicações em outras linguagens precisam de runtimes pesados, Go compila para binários nativos que rodam em containers minimalistas de 10-20MB. Neste guia, você vai aprender a criar imagens Docker otimizadas, seguras e prontas para produção.
Por Que Docker com Go?
Vantagens da Combinação
| Aspecto | Go + Docker | Outras Linguagens |
|---|---|---|
| Tamanho da Imagem | 10-50MB | 100MB-1GB+ |
| Tempo de Startup | < 100ms | 1-30 segundos |
| Memory Footprint | 10-50MB | 100MB-1GB |
| Runtime | Nenhum (binário nativo) | JVM, Node, Python |
| Security Surface | Mínima (scratch/alpine) | Grande (SO completo) |
Casos de Uso
- Microserviços: Deploy rápido e escalável
- CI/CD: Builds reproduzíveis
- Desenvolvimento: Ambiente consistente
- Produção: Alta densidade de containers
Dockerfile Básico para Go
O Problema: Dockerfile Inicial
# ❌ NÃO FAÇA ISSO - Imagem gigante (~1GB)
FROM golang:1.21
WORKDIR /app
COPY . .
RUN go build -o main .
CMD ["./main"]
Problemas:
- Imagem de 1GB+ com toolchain completa
- Código fonte exposto
- Dependências de desenvolvimento inclusas
- Security surface enorme
A Solução: Multi-Stage Build
# ✅ FAÇA ISSO - Imagem otimizada (~15MB)
# Stage 1: Build
FROM golang:1.21-alpine AS builder
WORKDIR /app
# Instalar dependências de build
RUN apk add --no-cache git ca-certificates tzdata
# Copiar dependências primeiro (cache eficiente)
COPY go.mod go.sum ./
RUN go mod download
# Copiar código e buildar
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .
# Stage 2: Runtime minimal
FROM scratch
# Certificados CA para HTTPS
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
WORKDIR /root/
# Copiar apenas o binário
COPY --from=builder /app/main .
# Porta exposta
EXPOSE 8080
# Usuário não-root (não funciona em scratch sem configuração extra)
# USER 65534:65534
CMD ["./main"]
Resultado: 1GB → 15MB (98% menor!)
Entendendo Multi-Stage Builds
Como Funciona
┌─────────────────────────────────────────────────────────────┐
│ STAGE 1: BUILDER │
│ ┌─────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ golang:1.21 │───▶│ go mod build │───▶│ binário main │ │
│ │ + git │ │ (cacheável) │ │ (estático) │ │
│ │ + certs │ └──────────────┘ └──────────────┘ │
│ └─────────────┘ │ │
│ │ COPY --from=builder │
│ ▼ │
├─────────────────────────────────────────────────────────────┤
│ STAGE 2: RUNTIME │
│ ┌─────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ scratch │───▶│ certs + tz │───▶│ ./main │ │
│ │ (vazio) │ │ (mínimo) │ │ (executa) │ │
│ └─────────────┘ └──────────────┘ └──────────────┘ │
│ │
│ Tamanho final: ~10-20MB │
└─────────────────────────────────────────────────────────────┘
Base Images para Go
| Base | Tamanho | Use Case |
|---|---|---|
scratch | ~0MB | Binários puros, máxima segurança |
distroless/static | ~2MB | Google distroless, debug fácil |
alpine | ~5MB | Shell disponível, pacotes apk |
debian:slim | ~50MB | Compatibilidade máxima |
Dockerfile Otimizado para Produção
Template Completo
# syntax=docker/dockerfile:1
###############################
# STAGE 1: Build dependencies
###############################
FROM golang:1.21-alpine AS deps
RUN apk add --no-cache git ca-certificates tzdata
WORKDIR /app
# Copiar apenas arquivos de dependência (cache eficiente)
COPY go.mod go.sum ./
RUN go mod download && go mod verify
###############################
# STAGE 2: Build
###############################
FROM golang:1.21-alpine AS builder
# Instalar build dependencies
RUN apk add --no-cache git ca-certificates
WORKDIR /app
# Copiar dependências cacheadas
COPY --from=deps /go/pkg /go/pkg
# Copiar código
COPY . .
# Build flags para binário otimizado
ARG VERSION=dev
ARG BUILD_TIME
ARG GIT_COMMIT
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
-ldflags="-w -s \
-X main.version=${VERSION} \
-X main.buildTime=${BUILD_TIME} \
-X main.gitCommit=${GIT_COMMIT}" \
-a -installsuffix cgo \
-o main .
###############################
# STAGE 3: Security scan
###############################
FROM alpine:3.18 AS security
RUN apk add --no-cache curl
# Baixar e verificar binário (opcional)
COPY --from=builder /app/main /tmp/main
RUN chmod +x /tmp/main
###############################
# STAGE 4: Runtime
###############################
FROM scratch
# Metadata
LABEL org.opencontainers.image.title="Minha App Go"
LABEL org.opencontainers.image.description="API em Go"
LABEL org.opencontainers.image.version="${VERSION}"
# Certificados para HTTPS
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
# Timezone data
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
# Binário
COPY --from=builder /app/main /app/
WORKDIR /app
# Porta
EXPOSE 8080
# Health check (não suportado em scratch sem binário)
# HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
# CMD ["/app/main", "health"]
ENTRYPOINT ["/app/main"]
Build com Argumentos
# Build com metadados
docker build \
--build-arg VERSION=1.2.3 \
--build-arg BUILD_TIME=$(date -u +%Y-%m-%dT%H:%M:%SZ) \
--build-arg GIT_COMMIT=$(git rev-parse --short HEAD) \
-t minhaapp:v1.2.3 .
Docker Compose para Desenvolvimento
docker-compose.yml Completo
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile
target: builder # Usar stage de build para dev
ports:
- "8080:8080"
environment:
- ENV=development
- DB_HOST=postgres
- REDIS_HOST=redis
volumes:
- .:/app # Hot reload
- /go/pkg:/go/pkg # Cache de dependências
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_started
command: go run . # Usar go run para hot reload
networks:
- backend
# Hot reload com air (github.com/cosmtrek/air)
app-air:
build:
context: .
dockerfile: Dockerfile.dev
ports:
- "8080:8080"
volumes:
- .:/app
environment:
- ENV=development
profiles:
- air # docker-compose --profile air up
postgres:
image: postgres:15-alpine
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: secret
POSTGRES_DB: appdb
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
- ./migrations:/docker-entrypoint-initdb.d
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app"]
interval: 5s
timeout: 5s
retries: 5
networks:
- backend
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
networks:
- backend
# Observabilidade
prometheus:
image: prom/prometheus:latest
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
networks:
- backend
grafana:
image: grafana/grafana:latest
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
volumes:
- grafana_data:/var/lib/grafana
networks:
- backend
volumes:
postgres_data:
redis_data:
grafana_data:
networks:
backend:
driver: bridge
Dockerfile para Desenvolvimento
# Dockerfile.dev - Para desenvolvimento com hot reload
FROM golang:1.21-alpine
# Instalar air para hot reload
RUN go install github.com/cosmtrek/air@latest
WORKDIR /app
# Cache de dependências
COPY go.mod go.sum ./
RUN go mod download
# Configuração do air
COPY .air.toml .
EXPOSE 8080
CMD ["air", "-c", ".air.toml"]
.air.toml (Configuração)
root = "."
testdata_dir = "testdata"
tmp_dir = "tmp"
[build]
args_bin = []
bin = "./tmp/main"
cmd = "go build -o ./tmp/main ."
delay = 1000
exclude_dir = ["assets", "tmp", "vendor", "testdata"]
exclude_file = []
exclude_regex = ["_test.go"]
exclude_unchanged = false
follow_symlink = false
full_bin = ""
include_dir = []
include_ext = ["go", "tpl", "tmpl", "html"]
kill_delay = "0s"
log = "build-errors.log"
send_interrupt = false
stop_on_root = false
[color]
app = ""
build = "yellow"
main = "magenta"
runner = "green"
watcher = "cyan"
[log]
time = false
[misc]
clean_on_exit = false
[screen]
clear_on_rebuild = false
Boas Práticas de Produção
1. Non-Root User
# Para alpine base
FROM alpine:3.18
# Criar usuário não-root
RUN addgroup -g 65534 -S appgroup && \
adduser -u 65534 -S appuser -G appgroup
COPY --from=builder /app/main /app/
# Mudar ownership
RUN chown -R appuser:appgroup /app
USER appuser:appgroup
CMD ["/app/main"]
2. Health Checks
# Com alpine ou distroless com shell
FROM alpine:3.18
COPY --from=builder /app/main /app/
COPY --from=builder /app/healthcheck /app/ # Binário separado
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD /app/healthcheck || exit 1
CMD ["/app/main"]
// healthcheck.go - Binário pequeno para healthcheck
package main
import (
"fmt"
"net/http"
"os"
)
func main() {
resp, err := http.Get("http://localhost:8080/health")
if err != nil || resp.StatusCode != 200 {
fmt.Println("unhealthy")
os.Exit(1)
}
fmt.Println("healthy")
}
3. Graceful Shutdown
// main.go - Suporte a graceful shutdown
package main
import (
"context"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
srv := &http.Server{Addr: ":8080"}
// Channel para sinais
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
// Iniciar servidor em goroutine
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("Server error: %v", err)
}
}()
// Esperar sinal
<-quit
log.Println("Shutting down server...")
// Graceful shutdown com timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatal("Server forced to shutdown:", err)
}
log.Println("Server exited")
}
4. Signal Handling para Docker
# Usar exec form para receber sinais corretamente
CMD ["/app/main"]
# NÃO usar shell form (não recebe sinais)
# CMD /app/main # ❌
5. Otimização de Cache
# Estrutura cache-friendly
FROM golang:1.21-alpine AS builder
WORKDIR /app
# 1. Copiar apenas go.mod/go.sum (mais estável)
COPY go.mod go.sum ./
RUN go mod download # Cacheado entre builds
# 2. Copiar código (muda frequentemente)
COPY . .
RUN go build -o main .
6. Security Hardening
# Usar imagens verificadas
FROM golang:1.21-alpine@sha256:... AS builder
# Não incluir tokens/segredos
ARG GITHUB_TOKEN # ❌ Não faça isso
# Limpar cache de pacotes
RUN apk add --no-cache git && \
go build -o main . && \
rm -rf /go/pkg/* # Limpar
CI/CD com Docker
GitHub Actions
# .github/workflows/docker.yml
name: Docker Build
on:
push:
branches: [main]
tags: ['v*']
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to Registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v4
with:
images: ghcr.io/${{ github.repository }}
- name: Build and push
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
platforms: linux/amd64,linux/arm64
Build Multi-Arquitetura
# Criar builder multi-plataforma
docker buildx create --name multiarch --use
docker buildx inspect --bootstrap
# Build para múltiplas arquiteturas
docker buildx build \
--platform linux/amd64,linux/arm64 \
--tag minhaapp:latest \
--push .
Kubernetes Deployment
Deployment YAML
apiVersion: apps/v1
kind: Deployment
metadata:
name: go-app
labels:
app: go-app
spec:
replicas: 3
selector:
matchLabels:
app: go-app
template:
metadata:
labels:
app: go-app
spec:
containers:
- name: app
image: minhaapp:v1.0.0
ports:
- containerPort: 8080
resources:
requests:
memory: "32Mi"
cpu: "50m"
limits:
memory: "128Mi"
cpu: "200m"
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 2
periodSeconds: 5
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 65534
capabilities:
drop:
- ALL
---
apiVersion: v1
kind: Service
metadata:
name: go-app
spec:
selector:
app: go-app
ports:
- port: 80
targetPort: 8080
type: ClusterIP
Troubleshooting
Problemas Comuns
| Problema | Causa | Solução |
|---|---|---|
standard_init_linux.go:228 | Binário não estático | CGO_ENABLED=0 |
exec user process caused "no such file or directory" | Bibliotecas dinâmicas | Usar FROM scratch com binário estático |
permission denied | Porta < 1024 + non-root | Usar porta > 1024 |
tls: failed to verify certificate | Certs ausentes | Copiar ca-certificates.crt |
unknown time zone | Timezone data ausente | Copiar /usr/share/zoneinfo |
Debug em Containers
# Dockerfile de debug (não use em produção)
FROM alpine:3.18
RUN apk add --no-cache strace gdb
COPY --from=builder /app/main /app/
CMD ["/app/main"]
# Inspecionar container
docker exec -it container_id sh
# Ver logs
docker logs -f container_id
# Inspecionar camadas
docker history minhaapp:latest
Comandos Úteis
# Build otimizado
docker build -t minhaapp:latest .
# Ver tamanho
docker images minhaapp:latest
# Análise de camadas
docker history minhaapp:latest
# Scan de segurança
docker scan minhaapp:latest
# Multi-stage build com cache
docker build --target builder -t minhaapp:builder .
# Exportar/importar
docker save minhaapp:latest | gzip > minhaapp.tar.gz
docker load < minhaapp.tar.gz
Checklist de Produção
- Multi-stage build configurado
- Imagem final usa scratch/alpine/distroless
- Binário compilado com
CGO_ENABLED=0 - Certificados CA incluídos (se necessário HTTPS)
- Usuário non-root configurado
- Health check implementado
- Graceful shutdown configurado
- Variáveis de ambiente documentadas
- Porta exposta documentada
- Labels OCI incluídos
- Scan de segurança passou
- Build reproducível (go.mod/go.sum)
Próximos Passos
Continue containerizando:
- Go Performance - Otimize antes de containerizar
- Go para Microserviços - Arquitetura distribuída
- Go Clean Architecture - Código preparado para containers
- Go e gRPC - Comunicação entre serviços
Containers leves, binários rápidos, deploys ágeis. Go + Docker é a combinação perfeita!