skip to content

What does `go mod vendor` create, and how does it change what `go build` uses?

level: juniorimportance: should knowfreq 52%

answer

  1. a directory at the repository root
  2. the go command notices it and switches
  3. only the packages you actually import
  4. an index file sits beside the copied source
  5. go build stops needing the network

basics

~20 s

go mod vendor copies the source of every package needed to build and test the main module into a top-level vendor directory, plus an index file, vendor/modules.txt. Builds then compile from that directory instead of the downloaded module cache.

solid answer

~40 s

`go mod vendor` walks every package the main module builds and tests, resolves them against the versions `go.mod` selects, and copies just those packages' source into a `vendor/` directory at the repository root. Alongside them it writes `vendor/modules.txt`, an index naming each module, its version, and which packages were copied from it. From Go 1.14 on, if a `vendor` directory exists and `go.mod` declares `go 1.14` or later, the `go` command selects vendor mode by itself: `go build`, `go test` and `go run` resolve imports from `vendor/` and never reach the network or the module cache. It copies only what is imported, not whole repositories, and not the dependencies' own test files. It is a regeneration command, not an incremental one — it resets the whole directory every time you run it.

code

text · 7 lines
text
vendor/
	modules.txt
	golang.org/
		x/
			text/
				unicode/
					norm/   # only this package was imported

go deeper

for a junior

Be ready to say what the command produces and where it lives: a vendor directory at the repository root holding copied dependency source, plus an index file. Know that its mere presence changes where the build gets packages from.

for a middle

Explain the mechanics: which packages get copied and which do not, why an index file is needed at all, and the fact that the directory is reset rather than updated. Mention that the switch to vendor mode is automatic, not configured.

for a senior

An interviewer expects you to connect it to operations: what a vendored repository buys you on a build machine with no network, and what obligation it creates — regenerating and committing on every dependency change, and never patching in place.

for a principal

Own the consequence for everyone else. Committing this directory changes how every engineer's build resolves imports and puts every upgrade into a pull request; be able to say who benefits from that and who pays for it.

## The problem vendoring solves By default a Go build resolves imports through the module system: `go.mod` names the modules and versions the build needs, and the `go` command finds that source in the local module cache, downloading it if it is not there. That works well when the machine doing the build can reach wherever those modules come from. It works badly when it cannot — a release build on an isolated machine, a build environment with no outbound access, or a rebuild of an old commit long after the fact. Vendoring answers that by putting the dependency source **inside the repository**. The command is: ``` go mod vendor ``` ## What it copies It does not copy modules wholesale. It starts from the packages of the main module — every package the module builds, plus everything their tests import — and follows imports transitively. Only the packages actually reached are copied. A dependency repository containing forty packages of which you import one contributes one package directory. The dependencies' own `_test.go` files, their `testdata`, their documentation and their version-control metadata are not copied either. This is why a vendored tree is usually far smaller than the sum of the modules it was built from. The result is a directory tree at the repository root, laid out by import path: ``` vendor/ modules.txt golang.org/ x/ text/ unicode/ norm/ ``` ## The index file Copied source files carry no record of which module or which version they came from — a `.go` file under `vendor/` looks exactly like any other `.go` file. So `go mod vendor` also writes `vendor/modules.txt`, which records, for each module: its module path and selected version, an annotation marking whether the main module requires it directly, and one line per package copied. That file is the only place the versions live once the tree is on disk, and the `go` command reads it rather than trying to infer anything from the files. ## How the build changes The key behaviour, and the part that surprises people, is that **nothing has to be configured**. Since Go 1.14, if a `vendor` directory is present at the module root and `go.mod` declares `go 1.14` or higher, the `go` command behaves as if `-mod=vendor` had been passed. Imports resolve out of `vendor/`; the module cache and the network are not consulted at all. An import that is not present under `vendor/` is an error rather than something to go and fetch. That means checking a `vendor` directory into a repository silently changes how every developer's `go build` resolves packages. It is a decision with consequences, not a local convenience. ## It is regenerated, never patched `go mod vendor` resets the directory: it removes what is there and writes it again from the modules `go.mod` selects. Two consequences follow. First, nothing updates `vendor/` for you. If you add an import of a new dependency, or change a version in `go.mod`, the vendored tree is now stale and you must re-run the command and commit the result. The build will tell you, loudly, that the two disagree. Second, any hand-edit you make under `vendor/` is temporary. It compiles — the `go` command has no idea it was not the original — and then vanishes the next time anyone regenerates. Patching a dependency by editing vendored source is therefore a change that works on your machine and disappears without warning. ## Practical notes - `go mod vendor -v` prints the modules and packages it copies to standard error, which is a quick way to see what actually came in. - The command does not modify `go.mod`; it only reads the module requirements and copies source accordingly. - Because vendored files are compiled exactly as they sit on disk, and are not re-checked against any recorded module hash during a build, the committed tree is the artifact's real input. Whatever is in the commit is what ships. ## The short version `go mod vendor` turns your dependency graph into files in your repository, plus an index describing them; and once those files exist, the `go` command quietly builds from them instead of from anything it would otherwise download.

  • Does `go mod vendor` copy whole dependency repositories?
    No. It copies only the packages the main module's packages import, transitively, plus what their tests need. Other packages in the same repository, the dependencies' own test files, their testdata and their documentation are left out. That is why a vendored tree is usually much smaller than the modules it came from.
  • After you add a new import, is `vendor/` updated automatically?
    No. Nothing regenerates it for you. A build in vendor mode simply fails, because the imported package is not present under `vendor/` and vendor mode does not go looking elsewhere. You re-run `go mod vendor` yourself and commit the result. The same applies after changing a version in `go.mod`.
  • What does `go mod vendor -v` print?
    It writes the names of the modules and packages it vendors to standard error as it copies them. It is useful for confirming what actually came in — for example checking that a module you expected to drop out after a dependency change really is gone from the tree, rather than assuming it from the diff.

It is like photocopying only the pages of the reference books your report cites, stapling them into the back, and adding a contents page saying which book and which edition each page came from.

saying these in an interview costs you the question

  • Says a vendored build still downloads dependencies at build time
  • Thinks vendor/ holds whole repositories including their tests
  • Believes you must pass a flag before builds use vendor/
  • Claims go mod vendor updates go.mod's require list
  • Assumes vendor/ is updated automatically when go.mod changes