The offline build problem
You push code to a production server behind a firewall. The server has no internet access. You run go build. The build fails because the toolchain cannot reach the module proxy to download dependencies. Your code works on your laptop, but the deployment environment is isolated. You need a way to ship your dependencies alongside your source code so the build succeeds anywhere.
Vendoring solves this by copying external packages into a vendor directory inside your project. The vendor directory acts as a local snapshot of your dependency tree. When the build runs, Go looks in vendor first. If the files are there, the build succeeds without network access.
How vendoring works
Vendoring is a copy operation. The go tool reads your go.mod file, determines which modules are required, and copies their source code into vendor/. The command is simple:
# Populate the vendor directory from go.mod
go mod vendor
The command reads go.mod and go.sum, downloads any missing modules from the proxy, and copies the source files into vendor/. It also generates vendor/modules.txt, a manifest that maps module paths to versions. The command is idempotent. Running it twice does nothing if the dependencies have not changed.
Vendor is a snapshot. Update the snapshot or the build lies.
The vendor directory structure
The vendor directory mirrors import paths. If your code imports github.com/lib/pq, the vendor directory contains vendor/github.com/lib/pq/.... The structure matches the module path, not the local file system layout of the dependency. This ensures imports resolve correctly regardless of where the project lives on disk.
The vendor/modules.txt file is the source of truth for what is vendored. It lists every module and its version. The go tool generates this file automatically. Do not edit it by hand. If you change go.mod, you must run go mod vendor again to update vendor/modules.txt.
Vendoring respects replace directives. If your go.mod contains a replace directive pointing to a local path or a different version, go mod vendor copies the replaced code into vendor/. This is useful for testing patches against a fork without changing the public dependency version.
Convention aside: vendor/modules.txt is generated metadata. Treat it like go.sum. Commit it to version control. Never hand-edit it.
Enforcing vendor mode
Creating the vendor directory is not enough. By default, the go tool may still use the module cache if vendor is missing or incomplete. You must explicitly tell the toolchain to use vendor mode. The flag is -mod=vendor.
# Build using only vendored dependencies
go build -mod=vendor ./cmd/myapp
The flag forces Go to use the vendor directory. If a dependency is missing from vendor, the build fails. This guarantees reproducibility. You can set this flag globally for your project using the GOFLAGS environment variable.
# Force vendor mode for all go commands in the session
export GOFLAGS="-mod=vendor"
go build ./...
go test ./...
The GOFLAGS variable persists the flag to all subcommands. This is a common convention in CI pipelines and team setups. It ensures that every developer and every build agent uses the same dependency resolution strategy.
In a CI workflow, you typically vendor dependencies as a step, then build with vendor mode. This ensures the cache is populated and the vendor directory is up to date before the build runs.
# GitHub Actions workflow snippet
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.22'
# Vendor dependencies to ensure reproducible builds
- run: go mod vendor
# Build using only the vendored dependencies
- run: go build -mod=vendor ./cmd/myapp
The vendor step ensures the cache is populated and vendor/ is up to date. The build step uses -mod=vendor to ignore the cache and use the snapshot.
GOFLAGS is the team agreement. Set it once, trust it everywhere.
Pitfalls and errors
Vendoring introduces a new source of inconsistency. The vendor directory must match go.mod and go.sum. If they drift apart, the build fails.
If go.mod requires module v1.2.0 but vendor/modules.txt lists module v1.1.0, the compiler rejects the build with go: inconsistent vendoring in vendor/modules.txt. This error protects you from building with stale dependencies. The fix is to run go mod vendor again.
If you update a dependency in go.mod but forget to run go mod vendor, the vendor directory contains the old code. The build might succeed if you are not using -mod=vendor, but you are shipping the wrong version. Always run go mod vendor after changing dependencies.
The go mod tidy command updates go.mod and go.sum. It removes unused dependencies and adds missing ones. It does not touch the vendor directory. The standard workflow is go mod tidy followed by go mod vendor.
# Clean up go.mod and go.sum, then update vendor
go mod tidy
go mod vendor
The tidy command ensures the manifest is accurate. The vendor command syncs the snapshot to the manifest.
Vendoring increases repository size. The vendor directory can contain megabytes of source code. Some teams ignore vendor/ in .gitignore. This is risky. If you ignore vendor, you must ensure every build environment vendors dependencies. This reintroduces network dependency and potential proxy failures. The Go team recommends committing vendor/ if you use it. The size trade-off is usually worth the reliability.
The worst vendor bug is the one that builds locally but fails in CI.
When to vendor
Vendoring is a tool for specific constraints. It adds maintenance overhead. You run go mod vendor after every dependency change. You commit more files. You deal with merge conflicts in vendor/. Use it only when the benefits outweigh the costs.
Use vendor when you deploy to air-gapped environments that cannot reach the module proxy. Use vendor when you need reproducible builds in CI pipelines that must not depend on external network state. Use vendor when you want to audit dependencies by inspecting source code directly in your repository. Use vendor when you use replace directives extensively and need the replaced code to be part of the build artifact.
Use the module cache when you develop locally with reliable network access and prefer a smaller repository. Use the module cache when your project has minimal dependencies and you trust the proxy to remain available. Use the module cache when your team values fast iteration over strict reproducibility and accepts that builds depend on the proxy.
The module proxy is a convenience. Vendor is a guarantee.