34.7 Deploying to Cloud Run, Fly.io, and AWS Lambda

Right, so you’ve built something brilliant in Go. It’s fast, it’s tested, and it works on your machine. Now we need to get it running somewhere that isn’t your machine, ideally without losing our minds. The good news is that Go’s static binary superpower makes this almost criminally easy compared to the dependency hell of other languages. The slightly-less-good news is that each platform has its own particular brand of kookiness. Let’s break it down.

34.6 go build Flags: -ldflags, -trimpath, and Version Injection

Right, let’s talk about making your Go binaries less of a mystery box and more of a well-labeled, efficient tool. Running go build is easy, but using it well is an art form. We’re going to bend the compiler and linker to our will with a few key flags. This isn’t just about building; it’s about building smart. The -ldflags Power Play The -ldflags flag is your direct line to the linker, the tool that takes all the compiled pieces of your program and stitches them into a single, executable binary. We use it to inject values at compile-time that would otherwise be tedious or impossible to set in your code.

34.5 Embedding Files with go:embed

Right, so you’ve built your slick Go application. It works on your machine (famous last words), and now you need to ship it. You could tell your users to also download a config/ directory, a templates/ folder, and maybe some funny little .json files and hope they put everything in the exact right spot. Or, you could stop living in the 1990s and use go:embed. This is one of those features that feels like cheating. Introduced in Go 1.16, go:embed gives you a compile-time mechanism to snatch up files from your filesystem and stuff them directly into your binary. The result? A truly self-contained application. No more worrying about the relative paths between your binary and its assets once it’s deployed. It’s all just… in there.

34.4 Distroless and Scratch Base Images

Alright, let’s talk about the promised land of containerized Go deployments: the tiny, hyper-secure base image. You’ve built a static binary. It’s a glorious, self-contained chunk of machine code. So why on earth would you slap it into a full-blown Ubuntu or Alpine image, complete with a package manager and a shell you’ll never use? You wouldn’t. That’s like using a cargo ship to deliver a single, perfect diamond. Enter scratch and its more sophisticated cousin, distroless.

34.3 Docker Multi-Stage Builds for Minimal Go Images

Right, so you’ve got this beautiful, statically compiled Go binary. It’s a single, self-contained marvel of engineering. And your first instinct is to throw it into a ubuntu:latest Docker image and call it a day. Don’t. I’ve seen it. We’ve all seen it. That image is going to be a whopping 800MB, and 799.9MB of that is stuff your binary will never, ever touch. It’s like buying a private jet to commute to your neighbor’s house.

34.2 Cross-Compilation: Building for Linux from macOS or Windows

Right, so you’ve written your Go masterpiece on your MacBook, it works perfectly, and now you need to run it on a Linux server somewhere in the cloud. You could try to install the entire Go toolchain on that server, clone your code, and run go build there. But let’s be honest, that’s a faff. It’s also a fantastic way to introduce inconsistencies and violate the principle of building once and deploying that same artifact everywhere.

34.1 Static Binaries: One File, No Runtime Dependencies

Right, let’s talk about static binaries. This is one of Go’s killer features, and if you’re coming from the world of Python, Ruby, or even Java, it’s going to feel like a superpower. The promise is simple: you run go build, and out pops a single, self-contained executable file. No need to install a runtime on the server, no worrying about whether the right version of lib-whatever is installed. You just scp the file over, run it, and it works. It’s the software equivalent of a packed lunch—no assembly required.

— joke —

...