Neste artigo
Deixe de instalar o Go na sua máquina. Sem poluição do
GOPATH, sem «funciona na minha máquina» e sem qualquer desvio de versões. Foi exatamente este o padrão que usámos para reescrever os agentes Java dos relatórios BigBlueButton Learning Analytics e para construir o Piler Go Agent seguro. Veja como distribuir binários estáticos usando apenas o Docker.
Por Ghazi Triki, RIADVICE
O problema do «basta instalar o Go»
Quando geríamos agentes em Java para os relatórios BigBlueButton Learning Analytics, enfrentávamos o clássico «inferno das dependências». A passagem para Go prometia simplicidade, mas só se não repetíssemos os mesmos erros. Precisávamos de um processo de compilação tão hermético como os binários que produzia.
Todas as equipas acabam por esbarrar nestes obstáculos:
- Desvio de versões. O Ahmed está na 1.24, o Omar na 1.26 e o servidor de produção na 1.25. Uma pequena alteração no compilador parte a compilação, mas só para o Ahmed.
- Máquinas poluídas.
GOPATH,GOMODCACHEe ferramentas instaladas globalmente (staticcheck,gofumpt) que diferem de posto de trabalho para posto de trabalho. - Integração difícil. Um novo engenheiro passa o primeiro dia a lutar com variáveis de ambiente e cadeias de ferramentas C para o
cgo.
Na RIADVICE, valorizamos a disciplina e a ordem. Queríamos um processo de compilação que o refletisse: limpo, previsível e partilhado.
A resposta habitual é «use o Docker», mas a maioria dos tutoriais só mostra como construir uma imagem de contentor. Não explicam como obter um binário simples, com ligação estática, na sua máquina, ou seja, o artefacto de que realmente precisa para um serviço systemd ou para uma implementação isolada da rede, sem nunca instalar localmente o compilador Go.
docker run --rm -v "$(pwd):/app" -w /app golang:1.26.5-trixie \
go build -o dist/myapp ./cmd/myappA imagem oficial golang é o compilador. O seu código-fonte é montado em /app. O binário é escrito em dist/myapp na sua máquina. Quando o comando termina, o contentor é destruído (--rm) e a sua máquina fica tão limpa como estava.
Experiência real: do BigBlueButton ao Piler
Não se trata apenas de teoria. Aplicámos este padrão a dois projetos críticos:
- Relatórios BigBlueButton Learning Analytics: reescrevemos agentes Java pesados como binários Go leves. As compilações com Docker permitiram-nos visar exatamente o ambiente Debian dos servidores BBB, sem precisarmos desse ambiente nos nossos portáteis.
- Piler Go Agent: para a nossa solução de arquivo de e-mail, construímos um agente com segurança reforçada. O processo de compilação tinha de ser tão seguro como o código. Ao fixar o compilador num SHA específico, garantimos que cada bit do binário estava justificado.
Porque é melhor do que instalar o Go
| Aspeto | go install na máquina | docker run golang:... |
|---|---|---|
| Versão do Go | A última que instalou | Fixada, idêntica para todos |
| GOPATH / cache | Polui ~/go | Camadas de contentor herméticas |
| Cadeia de ferramentas cgo | Exige configuração da máquina | Incluída na imagem oficial |
| Paridade com a CI | «Funciona na minha máquina» | Imagem idêntica, binário idêntico |
BINARY := mini-api
DIST := dist
# Version metadata derived from git
VERSION := $(shell tmp=$$(git describe --tags --always --dirty 2>/dev/null) && echo "$${tmp#v}" || echo dev)
COMMIT := $(shell git rev-parse --short HEAD 2>/dev/null || echo unknown)
BRANCH := $(shell git rev-parse --abbrev-ref HEAD 2>/dev/null || echo unknown)
BUILD_DATE := $(shell date -u +%Y-%m-%dT%H:%M:%SZ)
LDFLAGS := -X example.com/mini-api/internal/version.Version=$(VERSION) \
-X example.com/mini-api/internal/version.Commit=$(COMMIT) \
-X example.com/mini-api/internal/version.Branch=$(BRANCH) \
-X example.com/mini-api/internal/version.BuildDate=$(BUILD_DATE)
# Pinned toolchain image
GO_IMAGE := golang:1.26.5-trixie
# Run any command inside the Go toolchain container
define run_in_docker
docker run --rm -v "$(PWD):/app" -w /app $(GO_IMAGE)
endef
.PHONY: build test lint
build:
@mkdir -p $(DIST)
$(call run_in_docker) bash -c 'CGO_ENABLED=0 go build -buildvcs=false -ldflags="$(LDFLAGS)" -o $(DIST)/$(BINARY) ./cmd/$(BINARY)'
@echo "Built $(DIST)/$(BINARY) (version $(VERSION))"
test:
$(call run_in_docker) go test -race ./...
lint:
$(call run_in_docker) go vet ./...O Makefile: a fonte da disciplina
Usamos um Makefile para codificar as etapas de compilação. Repare que GO_IMAGE está fixada numa versão específica. Nunca usamos latest.
Os detalhes técnicos que contam
1. CGO_ENABLED=0 para binários estáticos
No agente Piler, precisávamos de binários capazes de correr em qualquer sistema, de um Debian recente a um servidor antigo. Desativar o CGO garante que o binário contém tudo aquilo de que precisa, sem dependências dinâmicas de bibliotecas da máquina.
2. -ldflags -X para a proveniência
O seu binário deve conseguir dizer-lhe exatamente de onde vem. Injetamos o commit git e a data de compilação no momento da ligação. Isto foi essencial para depurar os agentes BigBlueButton em dezenas de clusters diferentes.
3. Fixar a imagem do compilador
Na nossa cultura, acreditamos na consistência. Fixar golang:1.26.5-trixie em vez de usar latest significa que, se o Ahmed compilar o binário hoje e o Omar o compilar daqui a seis meses, o resultado é idêntico bit a bit.
Conclusão
O compilador é uma ferramenta e, como qualquer ferramenta, deve ficar no seu lugar. Ao passar a cadeia de ferramentas Go para o Docker, eliminámos toda uma categoria de erros do tipo «comigo funciona» e simplificámos a integração de cada novo engenheiro que entra na RIADVICE.
Quer esteja a reescrever agentes Java ou a construir uma API segura para o Piler, este padrão oferece a disciplina e a fiabilidade que os sistemas de produção exigem.
Ghazi Triki é o fundador da RIADVICE. Construímos integrações seguras e de alto desempenho para o BigBlueButton, o Piler e muito mais. Vamos construir algo extraordinário.





