In dit artikel
Installeer Go niet langer op uw host. Geen vervuiling van
GOPATH, geen “bij mij werkt het” en geen versieverschillen. Met precies dit patroon hebben we Java-agents herschreven voor de rapporten van BigBlueButton Learning Analytics en de beveiligde Piler Go Agent gebouwd. Zo levert u statische binaries op met alleen Docker.
Door Ghazi Triki, RIADVICE
Het probleem met “installeer gewoon Go”
Toen we Java-agents beheerden voor de rapporten van BigBlueButton Learning Analytics, liepen we tegen de klassieke “dependency hell” aan. De overstap naar Go beloofde eenvoud, maar alleen als we niet dezelfde fouten zouden herhalen. We hadden een buildproces nodig dat net zo hermetisch was als de binaries die het opleverde.
Elk team loopt vroeg of laat tegen deze muren aan:
- Versieverschillen. Ahmed werkt met 1.24, Omar met 1.26 en de productieserver draait 1.25. Een kleine wijziging in de compiler breekt de build, maar alleen bij Ahmed.
- Vervuilde hosts.
GOPATH,GOMODCACHEen globaal geïnstalleerde tools (staticcheck,gofumpt) die op elk werkstation anders zijn. - Moeizame onboarding. Een nieuwe engineer is de eerste dag kwijt aan het worstelen met omgevingsvariabelen en C-toolchains voor
cgo.
Bij RIADVICE hechten we waarde aan discipline en orde. We wilden een buildproces dat dat weerspiegelt: schoon, voorspelbaar en gedeeld.
Het standaardantwoord is “gebruik Docker”, maar de meeste tutorials laten alleen zien hoe u een containerimage bouwt. Ze laten niet zien hoe u een gewone, statisch gelinkte binary op uw host krijgt, het artefact dat u werkelijk nodig hebt voor een systemd-service of een air-gapped deployment, zonder ooit lokaal de Go-compiler te installeren.
docker run --rm -v "$(pwd):/app" -w /app golang:1.26.5-trixie \
go build -o dist/myapp ./cmd/myappDe officiële golang-image is de compiler. Uw broncode wordt als bind mount gekoppeld op /app. De binary wordt op uw host weggeschreven naar dist/myapp. Zodra het commando klaar is, wordt de container verwijderd (--rm) en is uw host weer net zo schoon als daarvoor.
Praktijkervaring: van BigBlueButton tot Piler
Dit is niet alleen theorie. We hebben dit patroon toegepast op twee cruciale projecten:
- BigBlueButton Learning Analytics Reports: we hebben zware Java-agents herschreven tot lichte Go-binaries. Dankzij builds op basis van Docker konden we precies de Debian-omgeving van de BBB-servers als doel kiezen, zonder die omgeving op onze laptops nodig te hebben.
- Piler Go Agent: voor onze oplossing voor e-mailarchivering hebben we een agent met verscherpte beveiliging gebouwd. Het buildproces moest net zo veilig zijn als de code. Door de compiler vast te zetten op een specifieke SHA wisten we zeker dat elke bit in de binary verantwoord was.
Waarom dit beter is dan Go installeren
| Aandachtspunt | go install op de host | docker run golang:... |
|---|---|---|
| Go-versie | Wat u het laatst hebt geïnstalleerd | Vastgezet, voor iedereen identiek |
| GOPATH / cache | Vervuilt ~/go | Hermetische containerlagen |
| cgo-toolchain | Vereist configuratie op de host | Meegeleverd in de officiële image |
| Pariteit met CI | “Bij mij werkt het” | Identieke image, identieke binary |
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 ./...De Makefile: de bron van discipline
We gebruiken een Makefile om de buildstappen vast te leggen. Merk op dat GO_IMAGE is vastgezet op een specifieke versie. We gebruiken nooit latest.
De technische details die ertoe doen
1. CGO_ENABLED=0 voor statische binaries
Voor de Piler-agent hadden we binaries nodig die overal konden draaien, van een moderne Debian tot een verouderde server. Door CGO uit te schakelen bevat de binary alles wat hij nodig heeft, zonder dynamische afhankelijkheden van bibliotheken op de host.
2. -ldflags -X voor herkomst
Uw binary moet u precies kunnen vertellen waar hij vandaan komt. We injecteren de git-commit en de builddatum tijdens het linken. Dat was essentieel bij het debuggen van BigBlueButton-agents op tientallen verschillende clusters.
3. De compilerimage vastzetten
In onze cultuur geloven we in consistentie. Door golang:1.26.5-trixie vast te zetten in plaats van latest te gebruiken, is de uitvoer bit voor bit identiek, ook als Ahmed de binary vandaag bouwt en Omar over zes maanden.
Conclusie
De compiler is een gereedschap, en zoals elk gereedschap hoort hij op zijn plaats te blijven. Door de Go-toolchain naar Docker te verplaatsen, hebben we een hele categorie “bij mij werkt het”-bugs uitgebannen en de onboarding vereenvoudigd voor elke nieuwe engineer die bij RIADVICE begint.
Of u nu Java-agents herschrijft of een beveiligde API voor Piler bouwt, dit patroon biedt de discipline en betrouwbaarheid die productiesystemen vereisen.
Ghazi Triki is de oprichter van RIADVICE. Wij bouwen krachtige, veilige integraties voor BigBlueButton, Piler en meer. Laten we samen iets moois bouwen.





