Přejít na obsah
Všechny články

Sestavování aplikací v Go pomocí Dockeru: přenositelný, hermetický toolchain

autor: Ghazi Triki · 9 min čtení

Sestavování aplikací v Go pomocí Dockeru
V tomto článku

Přestaňte instalovat Go na hostitelský systém. Žádné zaneřáděné GOPATH, žádné „u mě to funguje“ a nulový rozptyl verzí. Přesně tento postup jsme použili při přepisu agentů v Javě pro reporty BigBlueButton Learning Analytics a při vývoji zabezpečeného agenta Piler v Go. Ukážeme vám, jak dodávat statické binárky jen s pomocí Dockeru.

Autor: Ghazi Triki, RIADVICE

Problém s radou „prostě si nainstalujte Go“

Když jsme spravovali agenty v Javě pro reporty BigBlueButton Learning Analytics, narazili jsme na klasické „peklo závislostí“. Přechod na Go sliboval jednoduchost, ale jen pokud nezopakujeme stejné chyby. Potřebovali jsme proces sestavení, který bude stejně hermetický jako binárky, které vytváří.

Každý tým na tyto překážky dříve či později narazí:

  • Rozptyl verzí. Ahmed má 1.24, Omar 1.26 a produkční server 1.25. Drobná změna v kompilátoru rozbije sestavení, ale jen Ahmedovi.
  • Zaneřáděné hostitelské systémy. GOPATH, GOMODCACHE a globálně nainstalované nástroje (staticcheck, gofumpt), které se na každé pracovní stanici liší.
  • Pomalý nástup nových lidí. Nový inženýr stráví první den bojem s proměnnými prostředí a toolchainy jazyka C pro cgo.

V RIADVICE si ceníme disciplíny a pořádku. Chtěli jsme proces sestavení, který to odráží: čistý, předvídatelný a společný.

Standardní odpověď zní „použijte Docker“, většina návodů vám ale ukáže jen to, jak sestavit image kontejneru. Neukáže, jak na hostitelském systému získat obyčejnou, staticky linkovanou binárku, tedy artefakt, který skutečně potřebujete pro službu systemd nebo pro nasazení v izolovaném prostředí, aniž byste lokálně instalovali kompilátor Go.

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

Kompilátorem je oficiální image golang. Váš zdrojový kód je připojen přes bind mount do /app. Binárka se zapíše na hostitelský systém do dist/myapp. Po dokončení příkazu se kontejner odstraní (--rm) a hostitelský systém zůstane stejně čistý jako předtím.

Zkušenosti z praxe: od BigBlueButton po Piler

Nejde jen o teorii. Tento postup jsme uplatnili ve dvou klíčových projektech:

  1. Reporty BigBlueButton Learning Analytics: Těžkopádné agenty v Javě jsme přepsali do odlehčených binárek v Go. Díky sestavování v Dockeru jsme mohli cílit přesně na prostředí Debianu na serverech BBB, aniž bychom toto prostředí potřebovali na svých noteboocích.
  2. Agent Piler v Go: Pro naše řešení archivace e-mailů jsme vyvinuli agenta se zvýšeným zabezpečením. Proces sestavení musel být stejně bezpečný jako samotný kód. Připnutím kompilátoru na konkrétní SHA jsme zajistili, že každý bit v binárce má svůj doložený původ.

Proč je to lepší než instalovat Go

Hlediskogo install na hostitelidocker run golang:...
Verze GoTa, kterou jste naposledy nainstalovaliPřipnutá, pro všechny stejná
GOPATH / cacheZaneřádí ~/goHermetické vrstvy kontejneru
Toolchain pro cgoVyžaduje nastavení hostiteleSoučástí oficiálního image
Shoda s CI„U mě to funguje“Stejný image, stejná binárka
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 ./...

Makefile: zdroj disciplíny

Kroky sestavení zapisujeme do souboru Makefile. Všimněte si, že GO_IMAGE je připnutý na konkrétní verzi. Nikdy nepoužíváme latest.

Technické detaily, na kterých záleží

1. CGO_ENABLED=0 pro statické binárky

U agenta Piler jsme potřebovali binárky, které poběží na čemkoli, od moderního Debianu po starší server. Vypnutí CGO zajistí, že binárka obsahuje vše, co potřebuje, bez dynamických závislostí na knihovnách hostitele.

2. -ldflags -X pro doložení původu

Vaše binárka by vám měla umět přesně říct, odkud pochází. Commit z gitu a datum sestavení do ní vkládáme při linkování. Pro ladění agentů BigBlueButton napříč desítkami různých clusterů to bylo zásadní.

3. Připnutí image kompilátoru

V naší kultuře věříme v konzistenci. Když místo latest připneme golang:1.26.5-trixie, znamená to, že pokud Ahmed sestaví binárku dnes a Omar za šest měsíců, výstup bude bit po bitu totožný.

Závěr

Kompilátor je nástroj a jako každý nástroj patří na své místo. Přesunem toolchainu Go do Dockeru jsme odstranili celou kategorii chyb typu „u mě to funguje“ a zjednodušili nástup každého nového inženýra, který do RIADVICE přijde.

Ať už přepisujete agenty v Javě, nebo vyvíjíte zabezpečené API pro Piler, tento postup přináší disciplínu a spolehlivost, kterou produkční systémy vyžadují.


Ghazi Triki je zakladatelem RIADVICE. Vyvíjíme výkonné a bezpečné integrace pro BigBlueButton, Piler a další systémy. Pojďme společně vytvořit něco skvělého.

🔗 Stáhněte si golang-docker-boilerplate na GitHubu