I have a small ARM board in the airing cupboard doing nothing but serving a status page, and every time I want to ship a new build to it I forget the incantation and go digging through shell history. So here it is, written down, mostly for me.
Go's cross-compilation is genuinely one of its best features. No toolchain to install, no Docker dance, no apt install gcc-arm-linux-gnueabihf. You set two environment variables and build:
GOOS=linux GOARCH=arm GOARM=7 go build -o statuspage .
GOOS is the target OS, GOARCH is the architecture, and GOARM is the bit everyone forgets. For a Pi-class board you almost always want 7. Leave it off and Go assumes an older ARM variant, which runs but throws away the floating-point performance of the hardware you actually have. For a 64-bit board it's simply GOARCH=arm64 and you drop GOARM entirely.
The one caveat: this all works because the binary is pure Go. The moment you import something that needs cgo (SQLite drivers being the usual culprit), CGO_ENABLED=0 stops being free and you're back to needing a cross-compiler. For this little status page there's no cgo, so it's a clean static binary, scp it across, restart the service, done. Thirty seconds, no cloud build runner, no fuss.