Skip to Content
Course content

79: Building and Cross-Compiling Binaries

Click on the "Edit" button in the top corner of the screen to edit your slide content.

One of the things that made me fall in love with Go early on was the binary. Unlike Python or Node, where you're constantly wrestling with pip install or npm install on your production server, Go just gives you a single, statically linked file. You drop that file onto a server, and it just works. No runtime, no dependencies, no drama.

Writing a simple system probe

To see this in action, let's build a tiny utility. I want a tool that tells me exactly what environment it's running in—specifically the OS and the architecture. It's a simple program, but it's the perfect way to prove that our cross-compiled binaries are actually targeting the right systems.

package main

import (
	"fmt"
	"runtime"
)

func main() {
	fmt.Printf("Hello from %s/%s!\n", runtime.GOOS, runtime.GOARCH)
}


I'll call this sysprobe.go. If I'm sitting at my desk on a MacBook (Apple Silicon), I can just run go build sysprobe.go. This creates an executable named sysprobe in my current directory. When I run it, it tells me darwin/arm64. Easy.

The "Exec Format Error" trap

Here is where I usually trip up when I'm moving fast. A few years ago, I was building a tool for a Linux server and I remember thinking, "I'll just build it here and SCP it over." I ran the standard go build and uploaded the resulting binary to my Ubuntu VPS.

When I tried to run it on the server, I got this: bash: ./sysprobe: cannot execute binary file: Exec format error.

I spent five minutes questioning my sanity before remembering the obvious: I had built a macOS binary and tried to run it on Linux. The operating system had no idea how to interpret the Mach-O binary format of macOS because it was expecting an ELF binary. It's a classic mistake, but it's the exact reason why cross-compiling is a first-class citizen in Go.

Targeting Linux and Windows from a Mac

The beauty of Go is that you don't need a separate "build machine" for every OS. You just need to tell the Go toolchain which target you're aiming for using environment variables: GOOS (the target operating system) and GOARCH (the target architecture).

To fix my mistake and build for that Linux server, I'll run this in my terminal:

GOOS=linux GOARCH=amd64 go build -o sysprobe-linux sysprobe.go

Notice the -o flag there. I used it to name the output sysprobe-linux so I don't overwrite my local Mac version. If I were targeting a Windows machine, it's just as simple:

GOOS=windows GOARCH=amd64 go build -o sysprobe.exe sysprobe.go

I've personally found that if you're doing this often, it's worth throwing these into a simple Makefile or a shell script. You don't want to be typing these environment variables manually every single time you push a release.

Stripping the binary for production

One last tip: if you look at the file size of these binaries, they might feel a bit chunky. That's because Go includes debug information and a symbol table by default. For a production tool where every megabyte counts (like in a tiny Docker image), I like to strip those out using linker flags.

GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o sysprobe-linux sysprobe.go

The -s removes the symbol table and -w removes the DWARF debugging information. The binary will still run perfectly, but it'll be significantly smaller. Just keep in mind that if the app crashes, your stack traces won't be as descriptive, so I only do this for final release builds.




📋 Practical Task

Build a Cross-Platform Hardware Identifier

Your task is to create a utility called hwid. This program should print the current OS, the architecture, and the number of CPUs available to the process (using the runtime.NumCPU() function).

  • Write the code in a file named hwid.go.
  • Build a binary targeting Linux (amd64) and name the output hwid-linux.
  • Build a binary targeting Windows (amd64) and name the output hwid.exe.
  • Build a final Linux (amd64) binary that is stripped of debug symbols using -ldflags="-s -w", naming it hwid-linux-slim.

To verify your work, compare the file sizes of hwid-linux and hwid-linux-slim using your file explorer or the ls -lh command.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.