Go: Implantação do Go com o Docker

Última atualização: 2026-08-26

A compilação estática do Go torna essa linguagem ideal para implantações em contêineres — um arquivo binário mais uma imagem inicial resultam em uma imagem de produção de 12 MB.

Quando você precisa fazer a implantação de uma API em Go em um ambiente de produção, um Dockerfile bem projetado pode ajudar a otimizar o tamanho, reduzindo-o de uma imagem base de 1,2 GB para 12 MB.

1. Você aprenderá


2. A história real de um engenheiro de backend

(1) Pontos críticos: Uma imagem do Docker de 1,2 GB leva 5 minutos para ser implantada a cada vez

A API de comércio eletrônico do Bob está pronta para entrar em operação:

“Usei a abordagem mais prática: usei FROM golang:1.22 como imagem base, inseri COPY o código-fonte nela e compilei dentro do contêiner. A imagem tem 1,2 GB e leva 5 minutos para ser baixada a cada implantação. O pipeline de CI/CD leva 15 minutos, desde o commit até a implantação. Meu chefe disse: ‘A implantação é muito lenta — leva 10 minutos para reverter a mudança.’”

DOCKERFILE
# Bad approach: compile inside container, keep all build tools
FROM golang:1.22          # 800MB + compiler tools
WORKDIR /app
COPY . .
RUN go build -o server .
EXPOSE 8080
CMD ["./server"]          # Image 1.2GB! Includes compiler, dependencies, toolchain

(2) Solução Go: Compilação em várias etapas

DOCKERFILE
# Good approach: multi-stage build
# Stage 1: compile (use full Go image)
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o server .

# Stage 2: run (use minimal image)
FROM scratch
COPY --from=builder /app/server /server
EXPOSE 8080
CMD ["/server"]            # Image 12MB! Only the binary

(3) Resultados: antes e depois da otimização

Métrico De estágio único (Go: 1,22) Multiestágio (Scratch) Melhoria
Tamanho da imagem 1,2 GB 12 MB 100x
Pull de implantação 5 minutos 5 segundos 60x
Riscos de segurança Inclui compiladores e conjuntos de ferramentas Apenas binários Superfície de ataque mínima
Cache de compilação ❌ Compilação completa a cada vez ✅ Cache de dependências em camadas

3. Melhores práticas para o Dockerfile

(1) ▶ Exemplo: Dockerfile para Go em múltiplas etapas

DOCKERFILE
# ===== Stage 1: Build =====
FROM golang:1.22-alpine AS builder

# Set working directory
WORKDIR /app

# Copy dependency files first (leverage Docker cache)
COPY go.mod go.sum ./
RUN go mod download

# Copy source code
COPY . .

# Static compilation
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /app/server .

# ===== Stage 2: Run =====
FROM scratch

# Copy binary from builder stage
COPY --from=builder /app/server /server

# If timezone files are needed
# COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo

# If SSL certificates are needed
# COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/

EXPOSE 8080

# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD ["/server", "-health"]

CMD ["/server"]

(2) ▶ Exemplo: versão alpina

DOCKERFILE
# ===== Stage 1: Build =====
FROM golang:1.22-alpine 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 /app/server .

# ===== Stage 2: Run (Alpine) =====
FROM alpine:3.19

# Install runtime dependencies (if needed)
# RUN apk --no-cache add ca-certificates tzdata

COPY --from=builder /app/server /server

EXPOSE 8080
CMD ["/server"]

(3) Seleção de uma imagem base

Imagem Tamanho Segurança Casos de uso
scratch 0 MB ✅ Superfície de ataque mínima Compilado estaticamente em Go puro, sem dependências externas
alpine 5 MB ⚠️ musl libc Requer um shell, o curl, certificados, etc.
distroless 20 MB ✅ Minimalista + Ferramentas Requer certificado SSL e dados de fuso horário
golang:alpine 350 MB ❌ Para desenvolvimento Apenas para a fase de compilação
golang:1.22 800 MB Nunca use em ambiente de produção
💡 Dica: -ldflags="-s -w" pode reduzir o tamanho do binário: -s remove a tabela de símbolos, e -w remove as informações de depuração DWARF. Isso pode reduzir o tamanho do seu binário em mais 30–40%, sem afetar seu funcionamento.


4. Compilação cruzada

(1) ▶ Exemplo: Script de compilação cruzada

MAKEFILE
# Makefile
APP=server

.PHONY: build-all

# Build for current platform
build:
	CGO_ENABLED=0 go build -ldflags="-s -w" -o bin/$(APP) .

# Cross-compile for multiple platforms
build-all:
	# Linux amd64 (most common)
	CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o bin/$(APP)-linux-amd64 .
	# Linux arm64 (AWS Graviton / Apple M1)
	CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="-s -w" -o bin/$(APP)-linux-arm64 .
	# macOS
	CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags="-s -w" -o bin/$(APP)-darwin-amd64 .
	# Windows
	CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags="-s -w" -o bin/$(APP)-windows-amd64.exe

# Docker build
docker-build:
	docker build -t myapp:latest .
▶ Experimente

(2) Impacto de CGO_ENABLED

CGO_ENABLED Vantagens Desvantagens
=0 Compilado estaticamente, roda em qualquer plataforma, imagem mínima Não é possível usar bibliotecas C (como o driver C para o SQLite)
=1 (padrão) Biblioteca C disponível Requer o runtime de C; aumenta o tamanho da imagem
🔥 Erro comum: Se o seu código Go usa mattn/go-sqlite3 (driver C), definir CGO_ENABLED=0 fará com que a compilação falhe. Solução: Use um driver SQLite puro para Go (como modernc.org/sqlite) ou defina CGO_ENABLED=1 com a imagem alpine (requer a instalação do gcc e do musl-dev).


5. Docker Compose para vários serviços

(1) ▶ Exemplo: API de comércio eletrônico + Banco de dados + Redis

YAML
# docker-compose.yml
version: '3.8'

services:
  api:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "8080:8080"
    environment:
      - DB_HOST=db
      - DB_PORT=3306
      - DB_USER=app
      - DB_PASSWORD=secret
      - DB_NAME=shop
      - REDIS_ADDR=redis:6379
      - GIN_MODE=release
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "/server", "-health"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 10s

  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: rootpass
      MYSQL_DATABASE: shop
      MYSQL_USER: app
      MYSQL_PASSWORD: secret
    ports:
      - "3306:3306"
    volumes:
      - db_data:/var/lib/mysql
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 5

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 3s
      retries: 3

volumes:
  db_data:

6. Implantação no K8s

(1) ▶ Exemplo: Implantação mínima do K8s

YAML
# k8s-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: go-api
  labels:
    app: go-api
spec:
  replicas: 3
  selector:
    matchLabels:
      app: go-api
  template:
    metadata:
      labels:
        app: go-api
    spec:
      containers:
      - name: api
        image: myregistry/go-api:latest
        ports:
        - containerPort: 8080
        env:
        - name: DB_HOST
          value: "mysql-service"
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: db-secret
              key: password
        resources:
          requests:
            memory: "64Mi"
            cpu: "250m"
          limits:
            memory: "128Mi"
            cpu: "500m"
        livenessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /ready
            port: 8080
          initialDelaySeconds: 3
          periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
  name: go-api-service
spec:
  selector:
    app: go-api
  ports:
  - port: 80
    targetPort: 8080
  type: LoadBalancer
100%
flowchart TD
    subgraph Build ["Build Phase"]
        SRC[Source code] --> DEP[go mod download]
        DEP --> BUILD[go build]
        BUILD --> BIN[Binary 15MB]
    end
    subgraph Container ["Container Phase"]
        BIN --> CP[COPY to scratch]
        CP --> IMG[Image 12MB]
    end
    subgraph Deploy ["Deploy Phase"]
        IMG --> PUSH[Push to Registry]
        PUSH --> K8S[K8s Deployment]
        K8S --> POD[Pod 3 replicas]
    end
    Build --> Container --> Deploy

7. Exemplo completo: todo o processo de contêinerização de uma API de comércio eletrônico

(1) ▶ Exemplo: API completa com verificação de integridade

GO 📖 Somente leitura
// cmd/server/main.go (complete API with health check)
package main

import (
    "context"
    "encoding/json"
    "flag"
    "log"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"
)

func main() {
    healthFlag := flag.Bool("health", false, "run health check")
    flag.Parse()

    if *healthFlag {
        // Health check mode: check if service is reachable
        resp, err := http.Get("http://localhost:8080/health")
        if err != nil {
            os.Exit(1)
        }
        resp.Body.Close()
        os.Exit(0)
    }

    mux := http.NewServeMux()

    // Health check endpoint
    mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
    })

    mux.HandleFunc("GET /ready", func(w http.ResponseWriter, r *http.Request) {
        // Check if dependencies like database are ready
        w.Header().Set("Content-Type", "application/json")
        json.NewEncoder(w).Encode(map[string]string{"ready": "true"})
    })

    // Business endpoint
    mux.HandleFunc("GET /api/products", func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        json.NewEncoder(w).Encode(map[string]interface{}{
            "products": []string{"laptop", "mouse", "keyboard"},
        })
    })

    server := &http.Server{
        Addr:    ":8080",
        Handler: mux,
    }

    // Graceful shutdown
    go func() {
        sigCh := make(chan os.Signal, 1)
        signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
        <-sigCh
        log.Println("Shutting down...")
        ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
        defer cancel()
        server.Shutdown(ctx)
    }()

    log.Println("Service listening on :8080")
    if err := server.ListenAndServe(); err != http.ErrServerClosed {
        log.Fatal(err)
    }
}
56 linhas de lógica (limite de 40, somente leitura)

(2) Arquivo Dockerfile associado

DOCKERFILE
# Dockerfile
FROM golang:1.22-alpine AS builder
WORKDIR /app

# Cache dependencies
COPY go.mod go.sum ./
RUN go mod download

COPY . .

# Static compilation + strip debug info
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /app/server ./cmd/server

# === Run stage ===
FROM scratch

# Copy binary
COPY --from=builder /app/server /server

# If SSL certificates are needed (accessing external HTTPS APIs)
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/

EXPOSE 8080

# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD ["/server", "-health"]

CMD ["/server"]

(3) Arquivo .dockerignore

TEXT 📖 Somente leitura
# .dockerignore
.git
.gitignore
*.md
bin/
tmp/
Dockerfile
.dockerignore
💡 Dica: .dockerignore tem um impacto enorme na velocidade da compilação — ele exclui arquivos desnecessários, reduzindo o tamanho do contexto enviado ao daemon do Docker. Adicionar o diretório .git (que pode ter centenas de MB) pode acelerar significativamente as compilações.


❓ Perguntas Frequentes

P: Por que a compilação em múltiplas etapas reduz o tamanho da imagem? R: A primeira etapa (builder) usa a imagem completa do Go para compilar os binários, enquanto a segunda etapa (run) apenas copia os binários compilados para uma imagem base mínima. As ferramentas de compilação (compiladores, gerenciadores de dependências, código-fonte) não aparecem na imagem final — apenas os próprios binários.

P: A imagem “scratch” é segura? R: A imagem “scratch” é a mais segura — ela está completamente vazia, sem shell, bibliotecas ou ferramentas. Um invasor não pode executar nenhum comando dentro do contêiner. No entanto, se o seu programa precisar de um certificado SSL, dados de fuso horário ou um shell, você precisará copiar esses arquivos da etapa de compilação ou mudar para alpine/distroless.

P: O que significa CGO_ENABLED=0? R: Por padrão, o Go usa vinculação dinâmica (CGO_ENABLED=1, utilizando o runtime C). Definir CGO_ENABLED=0 faz com que o Go gere um binário totalmente estático — que não depende de nenhuma biblioteca externa. Isso permite que o binário seja executado em qualquer sistema Linux, incluindo scratch.

P: Como faço para usar a compilação cruzada? R: Defina as variáveis de ambiente GOOS (SO de destino) e GOARCH (arquitetura de destino). GOOS=linux GOARCH=amd64 go build compila um binário para Linux no macOS. O Go suporta quase todas as combinações de plataformas. A compilação cruzada é mais simples quando CGO_ENABLED=0.

P: Qual é a diferença entre um check de integridade e um probe de prontidão? R: Um check de integridade verifica se um processo está em bom estado — caso contrário, o K8s reinicia o Pod. Um probe de prontidão verifica se um serviço está pronto para aceitar tráfego — caso não esteja pronto, o K8s remove o Pod do serviço. Utilize probes de prontidão durante a fase de inicialização e probes de atividade durante a fase de execução.

P: Qual é a relação entre o Docker e o K8s? Preciso aprender sobre o K8s? R: O Docker é um ambiente de execução de contêineres — ele empacota aplicativos em unidades padronizadas. O K8s é uma plataforma de orquestração de contêineres — ele gerencia a implantação, o dimensionamento e as verificações de integridade de vários contêineres. Projetos pequenos precisam apenas do Docker (ou do Docker Compose), enquanto projetos grandes exigem o K8s. Este curso aborda apenas exemplos de orquestração do K8s e não se aprofunda nos conceitos do K8s.

P: Qual é o tamanho típico das imagens do Docker em Go? R: Binários estáticos do Go + scratch ≈ 10–20 MB. Aplicativos em Python (baseados em python:3.12) ≈ 300 MB. Um aplicativo em Node.js (baseado em node:22) ≈ 400 MB. Um aplicativo Java (baseado em amazoncorretto:21) ≈ 500 MB. As imagens do Go apresentam uma vantagem significativa em termos de tamanho.


📖 Resumo


📝 Exercícios

  1. Básico (Dificuldade ⭐): Escreva um Dockerfile (compilação em múltiplas etapas) para um serviço HTTP simples em Go. Compile a imagem e verifique se ela pode ser acessada usando docker run -p 8080:8080. Use docker images para verificar o tamanho da imagem.

  2. Avançado (Dificuldade ⭐⭐): Implementar a containerização full-stack de uma aplicação em Go com um banco de dados. Requisitos: (1) Três serviços: API em Go, MySQL e Redis; (2) orquestração via docker-compose.yml; (3) Implementar uma verificação de integridade para confirmar se o banco de dados e o Redis estão prontos antes de iniciar a API; (4) Usar os.Getenv no código Go para ler as informações de conexão com o banco de dados; (5) Usar .dockerignore para excluir arquivos desnecessários.

  3. Desafio (Dificuldade ⭐⭐⭐): Implemente um pipeline completo de CI/CD (implementação conceitual; não é necessária CI de fato). Requisitos: (1) O Makefile deve suportar make build (compilação cruzada para linux/amd64 + linux/arm64), make docker-build e make docker-プッシュ; (2) Compilações com Dockerfile em múltiplos estágios; (3) Otimização do cache de compilação para cada estágio; (4) K8s deployment.yaml + service.yaml; (5) Inclui sondas de atividade e prontidão; (6) Limites de recursos (requests e limits).

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%