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

AspectoGo + DockerOutras Linguagens
Tamanho da Imagem10-50MB100MB-1GB+
Tempo de Startup< 100ms1-30 segundos
Memory Footprint10-50MB100MB-1GB
RuntimeNenhum (binário nativo)JVM, Node, Python
Security SurfaceMí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

BaseTamanhoUse Case
scratch~0MBBinários puros, máxima segurança
distroless/static~2MBGoogle distroless, debug fácil
alpine~5MBShell disponível, pacotes apk
debian:slim~50MBCompatibilidade 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

ProblemaCausaSolução
standard_init_linux.go:228Binário não estáticoCGO_ENABLED=0
exec user process caused "no such file or directory"Bibliotecas dinâmicasUsar FROM scratch com binário estático
permission deniedPorta < 1024 + non-rootUsar porta > 1024
tls: failed to verify certificateCerts ausentesCopiar ca-certificates.crt
unknown time zoneTimezone data ausenteCopiar /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:

  1. Go Performance - Otimize antes de containerizar
  2. Go para Microserviços - Arquitetura distribuída
  3. Go Clean Architecture - Código preparado para containers
  4. Go e gRPC - Comunicação entre serviços

Containers leves, binários rápidos, deploys ágeis. Go + Docker é a combinação perfeita!