Go modules in depth

What go.mod and go.sum actually mean, how to upgrade dependencies safely, when to vendor — and how the Packages page fits a runtime that compiles ahead of time.

Go's dependency system is small and strict, which is exactly why it's worth understanding rather than copying commands blindly. Two files run the whole show — go.mod and go.sum — and once you know what each one does, upgrades, pinning, and vendoring stop being mysterious. This guide covers all of that, then connects it to how Go actually runs on Falix.

At a glance
You need A Falix server running the Go application; Go on your own computer helps
Plan Any — on free it runs while your session timer has time left, premium runs 24/7
Time Twenty minutes
New to the Go app? Go on Falix

The two files

go.mod is your project's manifest. It's created by go mod init example.com/myapp and describes your module and what it needs:

module example.com/myapp

go 1.22

require (
    github.com/go-chi/chi/v5 v5.0.12
    golang.org/x/text v0.14.0 // indirect
)
  • The module line is your module path (often a repo URL — it doesn't have to be public).
  • The go line is the language version your code targets.
  • require lists dependencies with exact versions. An // indirect comment means it's a dependency of a dependency, not something you imported directly.

go.sum is the integrity lock. For every module version in your build, it records a cryptographic checksum. When Go downloads a module, it verifies the download against go.sum — so nobody can swap the code under a version you already trusted. You don't edit it by hand; Go maintains it.

🎯 Good to know: Commit both files. go.mod decides which versions you get; go.sum guarantees you get the same bytes every time. A repo without go.sum builds, but you've thrown away the reproducibility Go handed you for free.

Adding and upgrading

Go picks versions using semantic versioning (vMAJOR.MINOR.PATCH) and, by default, the minimum version that satisfies everything — predictable, not "latest wins".

Command What it does
go get github.com/pkg/errors Add a dependency (latest release) and record it
go get github.com/pkg/[email protected] Pin an exact version
go get -u ./... Upgrade dependencies to newer minor/patch releases
go get -u=patch ./... Upgrade to newer patch releases only (safest)
go mod tidy Add anything imported but missing, remove anything no longer used, and sync go.sum

Run go mod tidy before you commit — it's the housekeeping step that keeps go.mod and go.sum honest about what your code actually imports.

⚠️ Heads up: A new major version (v2 and up) is a different module path in Go. Upgrading chi/v5 to a hypothetical v6 means importing github.com/go-chi/chi/v6 in your code, not just bumping a number. This is "semantic import versioning", and it's why you'll see /v5 in import paths.

Vendoring

go mod vendor copies every dependency's source into a vendor/ folder in your repo. With vendor/ present, Go builds from it instead of downloading anything.

  • Vendor when you want fully reproducible, network-free builds, or you need your dependencies committed alongside your code (audits, air-gapped builds, or a build step that can't reach the internet).
  • Skip it when you're happy for the build to fetch modules — the module cache and go.sum already give you reproducibility for most projects.

On Falix, vendoring has one concrete benefit: a Git build step that runs go build will use the committed vendor/ folder and won't need to reach out to the module proxy at all.

Private modules

Public modules just work. Private ones need Go to authenticate to your host, which it can't do with credentials it doesn't have. Two honest options:

  • Set GOPRIVATE=example.com/yourorg/* so Go skips the public proxy and checksum database for those paths — then provide git credentials in the environment where the build runs.
  • Or vendor the private code so it's already in the repo and no authenticated fetch is needed at build time. On Falix this is usually the simplest path.

The Falix layer

Two facts from Go on Falix shape how all of this plays out here:

  1. The Go application runs a prebuilt binary — nothing compiles on start. So go.mod changes don't take effect until you rebuild the app binary (build locally and upload, or run go build -o app as a Git post-deploy command).
  2. The Go server has a Packages page. Open Packages from your server menu, search for a module, and press Install — it runs go get, updates your go.mod and go.sum for you, and shows the job under Tasks. It's the easy way to add or upgrade a dependency without editing files by hand.

⚠️ Heads up: Because Go is compiled ahead of time, installing a package through the Packages page changes go.mod but not your running binary. Always rebuild (go build -o app) after a dependency change so the new module is actually baked into app.

Everything past the Falix-shaped parts is standard Go — the official Go Modules Reference is the complete source of truth for the resolver, replace directives, and the proxy protocol.


Next steps

Was this guide helpful?