I have a small ARM box running a couple of services, and for a long time I built those services on the box itself. This works until the box is a Pi-class machine and the build takes long enough to make a cup of tea, drink it, and regret your life choices. Go's cross-compilation makes the whole thing a non-issue, and I keep meeting people who don't realise how good it is, so here's the short version.
The headline is that for a pure-Go program, cross-compiling is two environment variables and nothing else:
GOOS=linux GOARCH=arm GOARM=7 go build -o myservice .
That's it. No toolchain to install, no cross-compiler, no apt install gcc-arm-linux-gnueabihf. The Go toolchain ships with everything it needs to target every platform it supports, out of the box. You build on your fast x86 laptop and scp the result to the box. The first time it just worked, I assumed I'd done something wrong.
The one variable that trips people up is GOARM. It selects the ARM version, and it only matters for 32-bit ARM (GOARCH=arm). The values are 5, 6 and 7, and they roughly map to floating-point capability: 5 is software float, 6 and 7 use the hardware FPU. Get it wrong, set 7 on a board that's really an ARMv6, and you don't get a friendly error, you get an "illegal instruction" at runtime, which is a confusing way to spend twenty minutes. If you're targeting a 64-bit board, use GOARCH=arm64 and GOARM is irrelevant, which is one fewer thing to get wrong. A quick cat /proc/cpuinfo on the target settles the question.
Where it stops being magic is cgo. The moment your program, or one of its dependencies, calls into C, Go needs an actual C cross-compiler for the target, because it has to compile that C. Now you're back to installing a toolchain and setting CC to point at it, and CGO_ENABLED=1 with a cross gcc. Plenty of pure-looking libraries pull in cgo through a dependency without advertising it, the classic being anything that touches SQLite or system DNS resolution. The tell is a build that compiled fine for your laptop and suddenly demands a C compiler the moment you change GOARCH.
My rule now is to keep these little services cgo-free wherever I can, which usually means picking the pure-Go SQLite driver or avoiding the dependency entirely, and setting CGO_ENABLED=0 explicitly so a transitive dependency can't quietly switch it back on behind my back. That keeps cross-compilation in its happy two-variable state, the build stays on the laptop, and the box in the cupboard just runs the binary. The tea, sadly, I now have to make for its own sake.