In questo articolo
Smetta di installare Go sulla macchina host. Niente
GOPATHinquinato, niente «sulla mia macchina funziona», nessuna deriva di versione. È lo schema esatto che abbiamo usato per riscrivere gli agenti Java dei report BigBlueButton Learning Analytics e per realizzare il Piler Go Agent sicuro. Ecco come distribuire binari statici usando soltanto Docker.
Di Ghazi Triki, RIADVICE
Il problema del «basta installare Go»
Quando gestivamo agenti basati su Java per i report BigBlueButton Learning Analytics, ci siamo scontrati con il classico «inferno delle dipendenze». Il passaggio a Go prometteva semplicità, ma solo a patto di non ripetere gli stessi errori. Ci serviva un processo di build ermetico quanto i binari che produceva.
Prima o poi ogni team va a sbattere contro questi ostacoli:
- Deriva di versione. Ahmed usa la 1.24, Omar la 1.26 e il server di produzione la 1.25. Una piccola modifica del compilatore rompe la build, ma solo per Ahmed.
- Host inquinati.
GOPATH,GOMODCACHEe strumenti installati globalmente (staticcheck,gofumpt) che differiscono da una postazione all’altra. - Inserimento difficoltoso. Un nuovo ingegnere passa il primo giorno a lottare con variabili d’ambiente e toolchain C per
cgo.
In RIADVICE diamo valore alla disciplina e all’ordine. Volevamo un processo di build che li rispecchiasse: pulito, prevedibile e condiviso.
La risposta abituale è «usate Docker», ma la maggior parte dei tutorial mostra soltanto come creare un’immagine di container. Non spiega come ottenere un semplice binario con link statico sulla macchina host, cioè l’artefatto che serve davvero per un servizio systemd o per un deployment air-gapped, senza mai installare il compilatore Go in locale.
docker run --rm -v "$(pwd):/app" -w /app golang:1.26.5-trixie \
go build -o dist/myapp ./cmd/myappL’immagine ufficiale golang è il compilatore. Il codice sorgente viene montato in bind su /app. Il binario viene scritto in dist/myapp sulla macchina host. Al termine del comando il container viene distrutto (--rm) e l’host resta pulito come prima.
Esperienza sul campo: da BigBlueButton a Piler
Non è solo teoria. Abbiamo applicato questo schema a due progetti critici:
- Report BigBlueButton Learning Analytics: abbiamo riscritto pesanti agenti Java sotto forma di binari Go leggeri. Le build basate su Docker ci hanno permesso di puntare esattamente all’ambiente Debian dei server BBB senza doverlo installare sui nostri portatili.
- Piler Go Agent: per la nostra soluzione di archiviazione delle e-mail abbiamo realizzato un agente con sicurezza rafforzata. Il processo di build doveva essere sicuro quanto il codice. Fissando il compilatore a uno SHA specifico, ci siamo assicurati che ogni bit del binario fosse tracciabile.
Perché è meglio che installare Go
| Aspetto | go install sull’host | docker run golang:... |
|---|---|---|
| Versione di Go | L’ultima che ha installato | Fissata, identica per tutti |
| GOPATH / cache | Inquina ~/go | Layer del container ermetici |
| Toolchain cgo | Richiede configurazione host | Inclusa nell’immagine ufficiale |
| Parità con la CI | «Sulla mia macchina funziona» | Stessa immagine, stesso binario |
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 ./...Il Makefile, fonte di disciplina
Usiamo un Makefile per codificare le fasi di build. Si noti come GO_IMAGE sia fissata a una versione specifica. Non usiamo mai latest.
I dettagli tecnici che contano
1. CGO_ENABLED=0 per i binari statici
Per l’agente Piler ci servivano binari in grado di funzionare ovunque, da una Debian recente a un server datato. Disattivare CGO garantisce che il binario contenga tutto ciò di cui ha bisogno, senza dipendenze dinamiche dalle librerie dell’host.
2. -ldflags -X per la provenienza
Un binario deve poter dire esattamente da dove proviene. Iniettiamo il commit git e la data di build in fase di linking. È stato fondamentale per il debug degli agenti BigBlueButton su decine di cluster diversi.
3. Fissare l’immagine del compilatore
La nostra cultura si fonda sulla coerenza. Fissare golang:1.26.5-trixie anziché usare latest significa che, se Ahmed compila il binario oggi e Omar lo compila tra sei mesi, il risultato è identico bit per bit.
Conclusione
Il compilatore è uno strumento e, come ogni strumento, va tenuto al suo posto. Spostando la toolchain Go in Docker abbiamo eliminato un’intera categoria di bug del tipo «da me funziona» e semplificato l’inserimento di ogni nuovo ingegnere che entra in RIADVICE.
Che si tratti di riscrivere agenti Java o di costruire un’API sicura per Piler, questo schema offre la disciplina e l’affidabilità richieste dai sistemi in produzione.
Ghazi Triki è il fondatore di RIADVICE. Realizziamo integrazioni sicure e ad alte prestazioni per BigBlueButton, Piler e molto altro: costruiamo insieme qualcosa di grande.





