---
title: "Go com Docker: Guia Completo 2026"
url: "https://golang.com.br/aprenda/golang-docker/"
markdown_url: "https://golang.com.br/aprenda/golang-docker.MD"
description: "Aprenda a containerizar aplicações Go com Docker. Multi-stage builds, Dockerfile otimizado, docker-compose e deploy de imagens mínimas."
date: "2026-02-21"
author: ""
---

# Go com Docker: Guia Completo 2026

Aprenda a containerizar aplicações Go com Docker. Multi-stage builds, Dockerfile otimizado, docker-compose e deploy de imagens mínimas.


# 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:

1. **Binários estáticos**: Go compila tudo em um único executável, sem dependências externas
2. **Compilação cruzada nativa**: compile para Linux mesmo estando no macOS ou Windows
3. **Imagens minúsculas**: com `scratch` ou Alpine, suas imagens ficam entre 5-15MB
4. **Inicialização instantânea**: sem JVM, sem runtime, sem warm-up
5. **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:

```bash
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:

```go
// 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
# 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:

```bash
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:

```dockerfile
# ===========================================
# 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.

```bash
# 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:

```dockerignore
# 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:

```yaml
# 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
# 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:

```yaml
# 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:

```bash
# 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:

```go
// 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:

```bash
# 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:

```go
// 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`:

```go
import _ "time/tzdata" // embute os dados de timezone no binário
```

### 3. Certificados CA para HTTPS

Sem certificados CA, requisições HTTPS externas falham:

```go
// 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:

```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
# =============================================
# 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:

```go
// 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:

```bash
docker build \
    --build-arg APP_VERSION=1.2.3 \
    -t minha-api:1.2.3 .
```

---

## Comandos Docker Essenciais

```bash
# 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 `scratch` ou 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
- **`.dockerignore`** evita 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](/aprenda/api-rest-go/) — Crie a API para containerizar
- [Go com PostgreSQL](/aprenda/golang-postgresql/) — Docker Compose com banco
- [Microservices com Go](/aprenda/golang-microservices/) — Arquitetura distribuída
- [Vagas DevOps Go](/vagas/devops/) — Vagas que pedem Go + Docker
