Por que seu projeto Go pede CI/CD como um cachorro pede para passear
Você escreveu um microsserviço incrível em Go. Ele voa, os testes passam, o código é bonito. Mas toda manhã você executa manualmente go build, envia o binário para o servidor via SSH e reza para que nada quebre. Familiar? Se sim — tenho uma novidade para você: você está gastando 15% da sua vida com algo que um robô pode fazer.
CI/CD (Integração Contínua / Entrega Contínua) não é apenas uma palavra da moda nas vagas de DevOps. É seu assistente pessoal que:
- executa testes a cada push no Git;
- compila o binário para Linux/amd64, mesmo que você use Mac;
- envia notificações no Telegram se o build falhar;
- faz deploy automático no servidor quando você faz merge de um PR na main.
Neste artigo, vamos configurar um pipeline CI/CD para Go do zero. Sem enrolação, sem "instale o Jenkins em 20 servidores". Apenas GitHub Actions, Docker e um pouco de mágica. Vamos lá!
O que vamos precisar
Para a configuração, precisaremos de:
- Um repositório no GitHub (você pode criar um de teste agora mesmo);
- Um projeto Go com módulos (vamos escrever um servidor HTTP simples);
- Uma conta no Docker Hub (ou qualquer outro registry);
- 5 minutos de atenção e vontade de automatizar a rotina.
Todo o código que usaremos está disponível publicamente. Você pode copiá-lo e adaptá-lo ao seu projeto. O importante é não esquecer de substituir os tokens e nomes de repositórios pelos seus.
Passo 1: Escrevendo uma aplicação Go simples
Para demonstração, vamos usar um servidor HTTP mínimo. Crie o arquivo main.go:
package main
import ( "fmt" "net/http")
func main() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello from Go CI/CD pipeline!") })
http.ListenAndServe(":8080", nil)}E o arquivo main_test.go para verificar o funcionamento:
package main
import ( "net/http" "net/http/httptest" "testing")
func TestHelloHandler(t *testing.T) { req := httptest.NewRequest("GET", "/", nil) w := httptest.NewRecorder()
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello from Go CI/CD pipeline!") })
http.DefaultServeMux.ServeHTTP(w, req)
if w.Code != http.StatusOK { t.Errorf("expected status 200, got %d", w.Code) }}Inicializamos o módulo:
go mod init github.com/seu-usuario/go-cicd-examplego mod tidyVerificamos se tudo funciona:
go test ./... -vgo run main.goSe você vir ok e a saudação em localhost:8080 — você é incrível. Vamos em frente.
Passo 2: Criando um Dockerfile para a aplicação Go
Containerização é uma etapa obrigatória no deploy moderno. Vamos compilar um binário estático (sem CGO) e empacotá-lo em uma imagem mínima baseada em scratch ou alpine. Isso nos dará uma imagem de cerca de 10–15 MB em vez de 300+ MB.
Crie o arquivo Dockerfile na raiz do projeto:
FROM golang:1.22-alpine AS builder
WORKDIR /appCOPY go.mod go.sum ./RUN go mod downloadCOPY . .RUN CGO_ENABLED=0 GOOS=linux go build -o main .
FROM alpine:latestRUN apk --no-cache add ca-certificatesWORKDIR /root/COPY --from=builder /app/main .EXPOSE 8080CMD ["./main"]Observe: usamos uma compilação multi-estágio. O primeiro estágio é a compilação no ambiente completo do Go. O segundo é uma imagem Alpine mínima, onde apenas o binário é copiado. Isso é o padrão da indústria para aplicações Go.
Vamos compilar a imagem localmente e verificar:
docker build -t go-cicd-example .docker run -p 8080:8080 go-cicd-exampleSe tudo estiver certo — vamos para o