Zum Inhalt springen
Alle Artikel

Go-Anwendungen mit Docker bauen: eine portable, hermetische Toolchain

von Ghazi Triki · 9 Min. Lesezeit

Go-Anwendungen mit Docker bauen
In diesem Artikel

Schluss mit Go auf dem Host. Kein verschmutzter GOPATH, kein „läuft bei mir“ und keine abweichenden Versionen. Genau mit diesem Muster haben wir Java-Agenten für die BigBlueButton Learning Analytics Reports neu geschrieben und den sicheren Piler Go Agent gebaut. So liefern Sie statische Binärdateien nur mit Docker aus.

Von Ghazi Triki, RIADVICE

Das Problem mit „installier doch einfach Go“

Als wir noch Java-basierte Agenten für die BigBlueButton Learning Analytics Reports betreuten, steckten wir in der klassischen „Dependency Hell“. Der Umstieg auf Go versprach Einfachheit, aber nur, wenn wir nicht dieselben Fehler wiederholten. Wir brauchten einen Build-Prozess, der so hermetisch ist wie die Binärdateien, die er erzeugt.

Früher oder später stößt jedes Team an diese Grenzen:

  • Abweichende Versionen. Ahmed arbeitet mit 1.24, Omar mit 1.26 und der Produktionsserver mit 1.25. Eine kleine Änderung am Compiler bricht den Build, aber nur bei Ahmed.
  • Verschmutzte Hosts. GOPATH, GOMODCACHE und global installierte Werkzeuge (staticcheck, gofumpt), die sich von Arbeitsplatz zu Arbeitsplatz unterscheiden.
  • Mühsames Onboarding. Neue Entwicklerinnen und Entwickler verbringen ihren ersten Tag damit, mit Umgebungsvariablen und C-Toolchains für cgo zu kämpfen.

Bei RIADVICE legen wir Wert auf Disziplin und Ordnung. Wir wollten einen Build-Prozess, der genau das widerspiegelt: sauber, vorhersehbar und für alle gleich.

Die übliche Antwort lautet „Nimm Docker“. Die meisten Tutorials zeigen aber nur, wie man ein Container-Image baut. Sie zeigen nicht, wie Sie eine einfache, statisch gelinkte Binärdatei auf Ihrem Host erhalten, also das Artefakt, das Sie für einen systemd-Dienst oder eine Air-Gap-Bereitstellung tatsächlich brauchen, ohne den Go-Compiler jemals lokal zu installieren.

Shell
docker run --rm -v "$(pwd):/app" -w /app golang:1.26.5-trixie \
    go build -o dist/myapp ./cmd/myapp

Das offizielle Image golang ist der Compiler. Ihr Quellcode wird per Bind-Mount unter /app eingebunden. Die Binärdatei landet in dist/myapp auf Ihrem Host. Sobald der Befehl beendet ist, wird der Container entfernt (--rm), und Ihr Host ist so sauber wie zuvor.

Aus der Praxis: von BigBlueButton bis Piler

Das ist keine graue Theorie. Wir haben dieses Muster in zwei zentralen Projekten eingesetzt:

  1. BigBlueButton Learning Analytics Reports: Wir haben schwergewichtige Java-Agenten als schlanke Go-Binärdateien neu geschrieben. Dank der Docker-basierten Builds konnten wir genau die Debian-Umgebung der BBB-Server ansteuern, ohne diese Umgebung auf unseren Laptops zu benötigen.
  2. Piler Go Agent: Für unsere E-Mail-Archivierungslösung haben wir einen sicherheitsgehärteten Agenten gebaut. Der Build-Prozess musste so sicher sein wie der Code. Indem wir den Compiler auf einen bestimmten SHA festgelegt haben, ist jedes Bit der Binärdatei nachvollziehbar.

Warum das besser ist, als Go zu installieren

Aspektgo install auf dem Hostdocker run golang:...
Go-VersionDie zuletzt installierteFestgelegt, für alle identisch
GOPATH / CacheVerschmutzt ~/goHermetische Container-Schichten
cgo-ToolchainEinrichtung auf dem Host nötigIm offiziellen Image enthalten
Gleichstand mit CI„Läuft bei mir“Identisches Image, identische Binärdatei
Makefile
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 ./...

Das Makefile: die Quelle der Disziplin

Wir halten die Build-Schritte in einem Makefile fest. Beachten Sie, dass GO_IMAGE auf eine bestimmte Version festgelegt ist. latest verwenden wir nie.

Die technischen Details, auf die es ankommt

1. CGO_ENABLED=0 für statische Binärdateien

Für den Piler-Agenten brauchten wir Binärdateien, die überall laufen, vom aktuellen Debian bis zum Altserver. Mit deaktiviertem CGO enthält die Binärdatei alles, was sie braucht, ohne dynamische Abhängigkeiten von Bibliotheken des Hosts.

2. -ldflags -X für die Herkunft

Ihre Binärdatei sollte Ihnen genau sagen können, woher sie stammt. Wir schreiben den Git-Commit und das Build-Datum beim Linken ein. Für die Fehlersuche bei BigBlueButton-Agenten über Dutzende verschiedener Cluster hinweg war das unverzichtbar.

3. Das Compiler-Image festlegen

Konsistenz gehört zu unserer Kultur. Wer golang:1.26.5-trixie festlegt, statt latest zu verwenden, erhält bitgenau dieselbe Ausgabe, ob Ahmed die Binärdatei heute baut oder Omar in sechs Monaten.

Fazit

Der Compiler ist ein Werkzeug, und wie jedes Werkzeug gehört er an seinen Platz. Seit die Go-Toolchain in Docker läuft, gibt es eine ganze Klasse von „läuft bei mir“-Fehlern nicht mehr, und das Onboarding ist für alle neuen Entwicklerinnen und Entwickler bei RIADVICE einfacher geworden.

Ob Sie Java-Agenten neu schreiben oder eine sichere API für Piler bauen: Dieses Muster bietet die Disziplin und Zuverlässigkeit, die Produktivsysteme verlangen.


Ghazi Triki ist der Gründer von RIADVICE. Wir bauen leistungsstarke, sichere Integrationen für BigBlueButton, Piler und mehr. Lassen Sie uns gemeinsam etwas Großartiges bauen.

🔗 golang-docker-boilerplate auf GitHub

Diesen Artikel teilen