Right, so you’ve decided you want to be a grown-up about your dependencies. Good for you. You’re tired of go.mod pointing to the great, flaky beyond—the internet—and you want a bit of control over your builds. You want to be able to run go build on a plane, in a bunker, or in the middle of a desert with a satellite phone as your only connection. This is where the vendor directory comes in. It’s not the shiny new thing (modules made it optional), but it’s the rock-solid, “I-know-exactly-what-is-in-this-build” option for hermetic and offline builds.

Think of the vendor directory as a local cache on steroids. When you run go mod vendor, the Go tooling does something beautifully simple: it takes every single package your module depends on (the entire transitive closure of your dependencies, down to golang.org/x/sys), and copies its source code into a folder named vendor at the root of your module. Your project becomes self-contained. The network is now a suggestion, not a requirement.

Why Bother with Vendor in the Age of Modules?

The go command can already use a proxy or your own local cache ($GOPATH/pkg/mod), so why would you clutter your project with this vendor/ monstrosity? Two words: determinism and isolation.

Your local module cache is a fantastic piece of engineering, but it’s a shared, mutable state. What if some over-eager cleanup script deletes it? What if you need to bisect a build from six months ago and your current cache has newer, potentially incompatible versions of those same dependencies? The vendor directory, committed to your version control (yes, you should commit it), is a frozen snapshot of exactly what was working at that point in time. It’s your ultimate “works on my machine” repellent because you’re literally shipping “your machine” (or at least, its dependencies) to everyone else.

How to Use It (It’s Not Magic)

Using it is dead simple. Navigate to your module’s root and run:

go mod vendor

This populates the vendor directory. To then build your project using only the code in that vendor directory, you use the -mod=vendor flag:

go build -mod=vendor ./...

This flag tells the go command: “I forbid you from looking anywhere else. Not the network, not your cache. Use only what’s in vendor/ or fail spectacularly.” It’s the digital equivalent of unplugging your Ethernet cable to prove a point.

The Dark Side: What to Watch Out For

It’s not all rainbows and deterministic builds. The vendor directory has its own set of quirks, most of which feel like the designers ran out of coffee the day they finalized this.

First, it’s not automatically updated. This is the biggest “gotcha.” If you update your go.mod by adding a new dependency or running go mod tidy, your vendor directory is now stale. It’s lying to you. You must re-run go mod vendor to re-synchronize it. Forgetting this is a classic mistake that leads to confusing “but it works on my machine” failures because you’re building with old code.

Second, it can be enormous. You’re pulling in the entire source tree for every dependency. If you vendor kubernetes/client-go, prepare for a several-hundred-megabyte vendor directory. This will bloat your repository and make every git clone operation a test of patience. You have to decide if the trade-off in determinism is worth the storage and bandwidth cost. For most libraries, it’s overkill. For critical application deployment, it’s often essential.

Third, it doesn’t vendor the Go toolchain. This is important. -mod=vendor only locks down your module dependencies. It doesn’t lock the Go version itself. For true, end-to-end hermetic builds, you need to combine vendor with a tool like go version checks, a Docker image, or a wider build system like Bazel that can also manage the compiler itself.

Best Practices for the Committed Vendor

If you’re going to commit this beast to git (and you should if you’re using it), do it right.

  1. Run go mod vendor and go build -mod=vendor in your CI/CD pipeline. This validates that your committed vendor directory is complete and your builds are truly hermetic. If a build passes without -mod=vendor but fails with it, your vendor directory is out of date. This check is non-negotiable.
  2. Don’t hand-edit it. The vendor directory is not a place for you to make quick patches. That way lies madness and unmaintainable forks. If you need to patch a dependency, use a replace directive in your go.mod to point to your local copy or a fork, then run go mod vendor to bring that patched version into vendor. This keeps the change documented and reproducible.
  3. Use .gitignore wisely. You generally should not have vendor/ in your .gitignore if you’re using it for determinism. The whole point is to commit it. However, if you’re a library author, you probably should ignore it, as it’s considered an implementation detail for end applications.

So, there you have it. The vendor directory: it’s big, it’s boring, it’s a bit of a blunt instrument, but when you absolutely, positively need to know exactly what code is in your build, it’s the only game in town. Now go vendor all the things. Just remember to go mod vendor again later. Seriously, don’t forget.