skip to content

What does go install example.com/cmd/[email protected] do that go install ./cmd/mytool does not?

level: middleimportance: must knowfreq 62%

answer

  1. the suffix changes more than the version
  2. which go.mod is in charge here
  3. it works with no module at all
  4. nothing in your repo records it
  5. and go get stopped doing this

basics

~20 s

The version suffix makes go install ignore the current module's go.mod entirely. It downloads that module at exactly v1.4.0, builds its command with that module's own dependency requirements, and installs the binary into GOBIN, changing nothing in your project.

solid answer

~50 s

`go install ./cmd/mytool` builds a main package **of the module you are standing in**, using your `go.mod` for dependency versions. The `pkg@version` form is a different mode: the go command constructs a throwaway module context, resolves `example.com/cmd/mytool` at exactly `v1.4.0`, and builds it with the dependency versions that module's own `go.mod` selects. Your `go.mod` and `go.sum` are neither read nor written, so installing a tool cannot perturb your project's dependency graph — and equally, your project records nothing about the tool. It works outside any module too, which is the point: it is how you get a command onto a machine. The target must be a main package, the binary lands in `$GOBIN` (else `$GOPATH/bin`), and `@latest` resolves to whatever the newest release happens to be **at the moment you run it**, which is why scripts should pin an exact version.

code

text · 4 lines
text
$ cd ~/work/payments          # a module with its own go.mod
$ go install example.com/cmd/[email protected]
$ git status --short          # go.mod and go.sum untouched
$ go env GOBIN GOPATH         # where the binary was written

go deeper

for a junior

Know that a tool is installed with go install and an explicit version suffix, that go get no longer installs binaries, and that the result lands in GOBIN or GOPATH/bin.

for a middle

Explain the isolation: the version form builds in a throwaway module context, taking dependency versions from the tool's own go.mod and leaving your go.mod and go.sum untouched.

for a senior

Be ready to reason about the consequence — a repository records nothing about tools installed this way, so reproducibility has to be created deliberately rather than assumed.

for a principal

Frame the tradeoff you are choosing: isolation from your dependency graph in exchange for versions that live outside it, and decide where in the team's workflow that version is written down and reviewed.

## Two commands that share a name and share almost nothing else `go install` has two distinct behaviours, and which one you get is decided by whether the argument carries an `@version` suffix. **Without a version** — `go install ./cmd/mytool`, `go install ./...` — the argument is interpreted relative to the **main module**: the module whose `go.mod` governs the directory you are in. Dependencies are resolved from that `go.mod`, exactly as `go build` and `go test` would. This is how you install your *own* commands. **With a version** — `go install example.com/cmd/[email protected]` — the go command deliberately steps outside your project. It behaves as if it were in an empty module that requires only `example.com/cmd/[email protected]`, so: - your `go.mod` and `go.sum` are **not consulted** and **not modified**; - dependency versions come from the requirements of *that module's* `go.mod`, selected the usual way (the minimum version that satisfies every requirement, not the newest available); - the command works with no module at all in sight — an empty directory, a fresh container, a bootstrap script. That isolation is the feature. Installing a linter or a code generator can never bump a dependency in the service you are working on, and a tool that needs an old library version does not drag your project backwards. ## Constraints of the version form - **It only installs commands.** The named package must be a main package; asking for a library gives you an error rather than a silent no-op. - **The version must be explicit.** `@v1.4.0`, `@latest`, a branch or a commit — but something. A bare path with no suffix, run outside a module, has no module context to resolve against and fails. - **Where it lands is unchanged**: `$GOBIN` if set, otherwise `$GOPATH/bin`, defaulting to `~/go/bin`. `go env GOBIN GOPATH` answers the "where did it go" question on any machine. - **Nothing in your repository records what you installed.** This is the flip side of isolation and it is the source of most real-world pain: the tool's version lives only in whatever shell history or setup script invoked it. ## go get no longer does this Older Go documentation, blog posts and Stack Overflow answers tell you to run `go get example.com/cmd/mytool` to install a command. In modern module-aware Go that no longer builds or installs anything. `go get` is now purely a dependency-management command: it adds, upgrades or removes requirements in the **main module's** `go.mod` (and updates `go.sum`). Run it against a command's package and you will get a dependency edit and no binary — or, outside a module, an error telling you to use `go install pkg@version`. The split is deliberate and worth being able to state: `go get` changes *what your module depends on*, `go install pkg@version` changes *what is on your machine*. They are no longer two faces of one operation. ## @latest is not a pin `@latest` is resolved at the moment the command runs, against the module proxy. Two engineers running the same setup script a month apart get different builds, and neither can tell from the script what they got. For anything reproducible — a setup script, a container image, a CI step — name an exact semantic version: ``` go install example.com/cmd/[email protected] ``` Then the version is reviewable, greppable and changes only in a commit somebody approved. ## Quick reference | | `go install ./cmd/mytool` | `go install example.com/cmd/[email protected]` | |---|---|---| | Needs a main module | yes | no | | Reads your go.mod | yes | no | | Can modify go.mod / go.sum | no | no | | Dependency versions from | your module | the tool's module | | Typical use | your own commands | third-party tools | ## What a weak answer looks like Saying that the version suffix "just picks a version" and otherwise behaves the same misses the entire point: it is a different resolution context. So does assuming the tool's version is somehow captured by your project because you ran the command inside it — it is not, and building a reproducible developer environment starts with accepting that.

  • You run go install example.com/cmd/[email protected] inside your service's directory. What changes in the repository?
    Nothing. The version form builds in an isolated module context, so your `go.mod` and `go.sum` are neither read nor written; the only effect is a new executable in `$GOBIN`. That isolation is why installing a tool can never bump one of your service's dependencies — and why nothing in the repository records which tool version anyone installed.
  • Why does go get example.com/cmd/mytool not put a binary on your PATH any more?
    Because `go get` is now purely a dependency-management command: it adds or updates a requirement in the main module's `go.mod` and updates `go.sum`, and builds nothing. Installing commands moved to `go install pkg@version`. Outside a module, `go get` has no `go.mod` to edit and simply errors, pointing you at the install form.
  • Which dependency versions are used when building a tool with the @version form?
    The ones selected from that tool module's own `go.mod` — the minimum versions satisfying its requirements, not the newest published and not whatever your project pins. So two people installing the same tool version get the same dependency graph regardless of what their own projects require, which is exactly what makes the tool build reproducible.
  • What happens if the package you name with a version suffix is not a main package?
    The install fails: the version form exists to build commands, and there is nothing to link for a library. Libraries are not installed at all in modern Go — they are compiled from source as dependencies of whatever imports them, with results kept in the build cache, so there is no artefact for go install to place.

saying these in an interview costs you the question

  • Thinks the @version form still reads the current module's go.mod
  • Believes installing a tool adds it to your go.mod
  • Says go get is still the way to install a command
  • Treats @latest as a reproducible pin
  • Assumes it can install a library package, not just a command