Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Lockfile Format

go2nix uses TOML lockfiles to pin module hashes. Both builder modes share the same lockfile format, generated by go2nix generate.

Format

# go2nix lockfile v2. Generated by go2nix. Do not edit.

[mod]
"github.com/foo/bar@v1.2.3" = "sha256-abc..."
"golang.org/x/sys@v0.20.0" = "sha256-def..."

[replace]
"github.com/foo/bar@v1.2.3" = "github.com/fork/bar"

Versioning

The header comment carries a format version (go2nix lockfile vN). The current version is v2. go2nix generate always writes the current version; older lockfiles should be regenerated. Nothing reads the version back, but the Go side rejects sections it does not know (parsing go2nix.toml: unknown keys: [...]), and generate reads the existing output file first to reuse its hashes — so delete an old-format lockfile, or write to a new path with -o, before regenerating.

Sections

[mod] — Module hashes. Each key is a composite "path@version" string, each value is a sha256-... SRI NAR hash of the module’s extracted source tree, i.e. $GOMODCACHE/<path>@<version>/ after go mod download and nothing else from the cache (the same tree the FOD fetcher produces).

[replace] — Module replacements (from go.mod replace directives). Maps a composite key — the original module path with the replacement’s version — to the replacement module path; the [mod] hash under that same key is the hash of the replacement’s tree. Only remote replacements are recorded; local replace directives (filesystem paths) are not included, and the modules they replace are not fetched. This section is read by the experimental builder and by go2nix build-modinfo; the default builder gets its replacements from the plugin and only reads [mod].

Composite keys

Module keys use "path@version" format (e.g., "golang.org/x/sys@v0.20.0"). This keeps each module uniquely identified and avoids collisions across versions.

Package graph resolution

The lockfile stores only module NAR hashes; the package graph is discovered separately (eval-time plugin in default mode, build-time go list in experimental mode — see Builder Modes).

Monorepo support

When go2nix generate is given multiple directories, all modules are merged into a single lockfile. Modules from different go.mod files coexist without conflict since each is uniquely keyed by "path@version".

When to regenerate

Regenerate the lockfile when — and only when — the module set changes:

  • you add, remove, or bump a require line in go.mod
  • a replace directive changes which remote module a path resolves to

generate and the staleness check look at go.mod only, so keep it tidy (go mod tidy); a change to go.sum alone neither needs nor triggers anything.

You do not need to regenerate after changing which packages import which other packages, adding a new local package, or editing .go files. The lockfile pins module hashes; the package graph is rediscovered on every evaluation (see Package graph resolution).

go2nix generate .   # rewrite go2nix.toml
go2nix check .      # verify go2nix.toml still matches go.mod, no rewrite

Lockfile-free builds

Default mode can build without a lockfile by setting goLock = null:

goEnv.buildGoApplication {
  src = ./.;
  goLock = null;
  pname = "my-app";
  version = "0.1.0";
}

When no lockfile is present, the Nix plugin is invoked with resolveHashes = true and computes a NAR hash for each module from go.sum + GOMODCACHE, returning a moduleHashes attrset that fills the role of the [mod] section. Module FODs are then keyed on those hashes.

This trades a checked-in pin file for zero lockfile maintenance. The build is still reproducible as long as go.sum is unchanged, but you lose the explicit, reviewable hash list.

Note: the build-time staleness check (mvscheck, see below) is skipped when goLock = null — there is no lockfile for go.mod to drift from. Module versions are read live from go.sum via the plugin on every evaluation, so a go get is reflected on the next nix build with nothing to regenerate. The standalone go2nix check subcommand compares go.mod with a lockfile and is not applicable in this mode.

Prefer a committed lockfile for anything you ship; lockfile-free is useful for ad-hoc builds and during early development.

Staleness detection

WhenWhatApplies toHow
GenerationThe module setAll modesgenerate takes every require line of go.mod, with replace applied, and hashes each module; it runs no go list and relies on go.mod being tidy
Nix evalPackage graph, and the modules it usesDefault onlybuiltins.resolveGoPackages runs go list at eval time; a module the graph uses and the lockfile lacks fails with attribute '"<module>@<version>"' missing
Build timeLockfile consistencyDefault, with lockfilelink-binary re-reads go.mod and checks every required module is present in the lockfile at the right version; skipped when goLock = null

In default mode a module that a package in the build imports but the lockfile does not have stops evaluation; requirements the package graph did not reach (another platform’s, or a test’s when doCheck is off) are caught at build time when link-binary validates the lockfile. There is no stale package graph: builtins.resolveGoPackages runs go list on every evaluation. In experimental mode go2nix resolve reports lockfile missing module <module>@<version> — regenerate with go2nix generate inside the recursive-nix sandbox, or go list or a module fetch fails there.

Run go2nix check <dir> or go2nix check --lockfile <path> <dir> to verify a lockfile without building.