There's a particular kind of waste that happens in Docker images and that everyone accepts as normal until someone points it out: shipping the compiler to production.
You need the Go toolchain to build your binary. You don't need it to run it. You need TypeScript, all your dev dependencies, and a working tsc setup to compile your app. You don't need any of that once the JavaScript exists. And yet, in a single-stage Dockerfile, all of it ends up in the image that gets pushed, pulled, and deployed.
Multi-stage builds are Docker's answer to this: use as many environments as you need to build your application, then carry only what's needed to run it.
The single-stage problem
The most natural Dockerfile for a Go application looks like this:






