Por que Go e Docker são a dupla ideal?
\n\nGo (ou Golang) é uma linguagem que, desde o seu nascimento, foi criada para arquiteturas modernas em nuvem. Antes mesmo de o Docker se tornar o padrão de facto, o Go já conseguia compilar em um binário estático que não exigia nenhuma dependência. E o Docker é uma tecnologia que permite empacotar uma aplicação com todo o seu ambiente em um contêiner leve. Quando você combina essas duas tecnologias, obtém uma solução praticamente ideal: tamanho mínimo de imagem, inicialização ultrarrápida e ausência total de conflitos de dependências.
\n\nNeste artigo, não vamos dar aulas teóricas sobre containerização. Em vez disso, escreveremos uma aplicação Go simples, a compilaremos em uma imagem Docker, a otimizaremos com uma build multi-stage e garantiremos que nosso contêiner esteja pronto para produção. Tudo isso com exemplos de código reais que você pode executar agora mesmo.
\n\nPasso 1: Escrevendo uma aplicação Go simples
\n\nPara começar, vamos criar um servidor HTTP simples em Go. Este será nosso "programa de teste". Crie um arquivo main.go com o seguinte conteúdo:
package main\n\nimport (\n "fmt"\n "log"\n "net/http"\n "os"\n)\n\nfunc main() {\n port := os.Getenv("PORT")\n if port == "" {\n port = "8080"\n }\n\n http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {\n fmt.Fprintf(w, "Olá do contêiner Go! 🐹")\n })\n\n log.Printf("Servidor iniciado na porta %s", port)\n log.Fatal(http.ListenAndServe(":"+port, nil))\n}\n\n\nEste código é clássico: ele escuta em uma porta (que pode ser definida pela variável de ambiente PORT) e responde a qualquer requisição com uma saudação simples. Agora, queremos que esta aplicação funcione no Docker, independentemente do sistema operacional em que o contêiner for executado.
Passo 2: Primeiro Dockerfile — abordagem ingênua
\n\nA maneira mais simples de "containerizar" uma aplicação Go é pegar a imagem completa do Go, copiar o código para ela e executar. Crie um arquivo Dockerfile:
# Usamos a imagem oficial do Go\nFROM golang:1.22\n\n# Definimos o diretório de trabalho\nWORKDIR /app\n\n# Copiamos os arquivos do projeto\nCOPY go.mod go.sum ./\nRUN go mod download\nCOPY . .\n\n# Compilamos a aplicação\nRUN go build -o main .\n\n# Executamos\nCMD ["./main"]\n\n\nVamos compilar a imagem e executar:
\n\ndocker build -t go-app:v1 .\ndocker run -p 8080:8080 go-app:v1\n\n\nAbra o navegador e acesse http://localhost:8080. Funciona? Ótimo. Mas há um problema: o tamanho da imagem. Vamos verificar:
docker images go-app:v1\n\n\nVocê verá algo como 1.2 GB. Isso é um desastre! A imagem contém um compilador Go inteiro, gerenciador de pacotes, utilitários do sistema — tudo o que não é necessário para executar o binário final. Em produção, essa imagem demoraria uma eternidade para carregar e ocuparia muito espaço.
Passo 3: Build multi-stage — o segredo das imagens leves
\n\nO Docker suporta a build multi-stage. É quando você usa várias instruções FROM em um único Dockerfile, e apenas o último estágio vai para a imagem final. O primeiro estágio — "de construção" — contém o compilador e todas as ferramentas. O segundo estágio é mínimo, por exemplo, scratch (imagem vazia) ou alpine.
Vamos reescrever nosso Dockerfile:
\n\n# Estágio 1: Compilação\nFROM golang:1.22 AS builder\n\nWORKDIR /app\n\n# Armazenamos em cache as dependências\nCOPY go.mod go.sum ./\nRUN go mod download\n\n# Copiamos o código-fonte e compilamos\nCOPY . .\nRUN CGO_ENABLED=0 GOOS=linux go build -o main .\n\n# Estágio 2: Imagem final\nFROM alpine:latest\n\nRUN apk --no-cache add ca-certificates\n\nWORKDIR /root/\n\n# Copiamos apenas o binário do primeiro estágio\nCOPY --from=builder /app/main .\n\nEXPOSE 8080\n\nCMD ["./main"]\n\n\nO que aconteceu aqui? Criamos dois estágios: