Cross-Building Go Programs with golang-crossbuild
When developing Go applications intended to run on multiple architectures (like ARM devices from an x86 machine), you need a reliable and reproducible way to cross-compile. In this post, Iβll share how I use the docker.elastic.co/beats-dev/golang-crossbuild
Docker image along with qemu-user-static
to build Go programs for ARM from my x86_64 development environment.
This guide is ideal for developers targeting Raspberry Pi or ARM-based IoT devices, especially when using CI/CD environments or containers for builds.
π§° Why Cross-Compiling with Docker?
Cross-compiling means building binaries for a different architecture than the host machine. While Go supports cross-compilation natively with environment variables like GOARCH
and GOOS
, Docker-based cross-compilation offers several advantages:
- β Consistent build environments
- β Easy integration with CI pipelines
- β Preinstalled cross-compilation toolchains
- β No need to pollute your host environment
π³ Using golang-crossbuild
from Elastic
Elastic maintains a powerful Docker image at docker.elastic.co/beats-dev/golang-crossbuild
. Itβs preconfigured with Go and cross-compilers for various architectures.
π οΈ Basic Usage
To compile a Go binary for linux/arm64
, use a command like:
1
2
3
4
5
6
docker run --rm \
-v "$(pwd)":/go/src/github.com/your/repo \
-w /go/src/github.com/your/repo \
docker.elastic.co/beats-dev/golang-crossbuild:1.22.10-darwin-arm64-debian11 \
--build-cmd "go build -o build/your-binary-linux-armv7 ./cmd/yourapp" \
-p "darwin/arm64"
π Explanation:
-v "$(pwd)"...
: Mounts your current directory into the container.-w
: Sets the working directory inside the container.- The image tag
1.22.10-darwin-arm64-debian11
specifies the target architecture. --build-cmd
: The actual build command to run inside.-p
: sets the target architecture
π§© What About QEMU?
To emulate ARM on x86, youβll need to register QEMU emulation:
βοΈ Installing and Registering QEMU
1
2
sudo apt-get install qemu-user-static
docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
This allows the container to run ARM binaries using QEMU, which is especially useful when youβre building and testing in one pipeline.
Tip: You only need to register QEMU once per system reboot.
π¦ Organizing Your Project
For best results, structure your Go project like this:
1
2
3
4
5
6
7
8
π your-go-app/
βββ cmd/
β βββ yourapp/
β βββ main.go
βββ go.mod
βββ go.sum
βββ build/
βββ (output binaries)
This layout plays nicely with most build scripts and keeps things clean.
π Example: Build Script
Hereβs a sample shell script that automates the build:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/bin/bash
set -e
REPO_PATH="github.com/your/repo"
OUT_DIR="./build"
mkdir -p "$OUT_DIR"
docker run --rm \
-v "$(pwd)":/go/src/$REPO_PATH \
-w /go/src/$REPO_PATH \
docker.elastic.co/beats-dev/golang-crossbuild:1.22.10-darwin-arm64-debian11 \
--build-cmd "go build -o $OUT_DIR/yourapp-linux-arm ./cmd/yourapp" \
-p "darwin/arm64"
Give it execute permissions with chmod +x build.sh
, then run ./build.sh
to compile.
π§ͺ Testing the Build on ARM Device
After the binary is built, copy it to your ARM device:
1
scp build/yourapp-linux-arm pi@raspberrypi.local:/home/pi/
Then SSH into the device and run:
1
2
chmod +x yourapp-linux-arm
./yourapp-linux-arm
If everything is set up right, it should work immediately.
π§ Final Thoughts
Cross-building Go programs with Docker and the Beats Crossbuild image makes targeting different architectures clean and reproducible. Using qemu-user-static
fills the last gap by allowing ARM binaries to be run or tested directly from an x86 machine or container.
If youβre developing software for embedded systems, Raspberry Pi, or distributing binaries across platforms, this workflow is a strong foundation.
Happy hacking!