Deploying a Python or Node.js application means shipping the application code AND ensuring the correct language runtime and every dependency is installed on the target machine — Docker & Containers' own domain covered exactly this problem. Go sidesteps a large part of it structurally, and this lesson is about why that matters for how Go software actually gets shipped.
3 min read
go build -o myapp main.go
./myapp # runs directly — no "python myapp.py", no "node myapp.js",
# no separate runtime needs to be installed on this machine at allgo build compiles a Go program into a single, statically linked native binary — one file containing the compiled program and everything it needs to run, with no separate Go runtime, no interpreter, and (by default) no dynamically-linked library dependencies that need to already exist on the target machine. Running a Python script requires a correctly-versioned Python interpreter already installed; running a compiled Go binary requires nothing beyond the binary itself and a compatible operating system and CPU architecture.
Docker & Containers' own domain covered choosing a minimal base image (slim, alpine, distroless) specifically to shrink a container image's size and attack surface. A Go application takes this to its logical extreme: a Docker image for a Go binary can use FROM scratch — literally an empty base image with nothing in it at all, not even a shell or a package manager — because the Go binary itself needs nothing else to run. This produces images that are often tens of megabytes instead of the hundreds of megabytes a Python or Node image with its full runtime and dependency tree typically needs, directly reducing both the image-scanning surface (image-security-scanning's own lesson) and the attack surface (there's no shell for an attacker to even reach, if they somehow gained code execution inside the container).
GOOS=linux GOARCH=amd64 go build -o myapp-linux main.go
GOOS=darwin GOARCH=arm64 go build -o myapp-mac main.go
GOOS=windows GOARCH=amd64 go build -o myapp.exe main.goBecause Go's compiler produces a self-contained binary with no dynamic runtime dependencies, it can cross-compile for a different target operating system and CPU architecture directly from a single development machine — setting GOOS/GOARCH environment variables and running go build produces a binary for Linux, macOS, or Windows, for amd64 or arm64, with no need for a virtual machine or container running the target OS just to produce the build. This is a genuinely distinctive capability most interpreted languages don't need (their "build" is largely just shipping the source) and most other compiled languages make considerably harder to set up.
go run, go test, and go vet: the toolchain is unusually unifiedgo run main.go # compiles and runs in one step, for quick local iteration
go test ./... # runs every test in every package, recursively
go vet ./... # static analysis — catches real bugs (format-string mismatches,
# unreachable code) the compiler itself doesn't flag as errorsGo ships its build tool, test runner, and a static-analysis tool (go vet) as one unified CLI with no separate package to install or configure — a deliberate extension of lesson 1's overall philosophy: rather than leaving tooling choices to the ecosystem (the way JavaScript has many competing bundlers and test runners), Go provides one standard, built-in answer for each, removing an entire category of "which tool should this project use" decision that other ecosystems' own domains in this curriculum have covered as a real, recurring source of friction.
Check your understanding
A quick comprehension check — not tracked, not graded, just for you.
1. What does `go build` produce?
2. Why can a Go Docker image realistically use `FROM scratch` (a completely empty base image)?
3. What does setting GOOS and GOARCH before `go build` accomplish?
Go