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
requireline ingo.mod - a
replacedirective 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 whengoLock = null— there is no lockfile forgo.modto drift from. Module versions are read live fromgo.sumvia the plugin on every evaluation, so ago getis reflected on the nextnix buildwith nothing to regenerate. The standalonego2nix checksubcommand comparesgo.modwith 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
| When | What | Applies to | How |
|---|---|---|---|
| Generation | The module set | All modes | generate 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 eval | Package graph, and the modules it uses | Default only | builtins.resolveGoPackages runs go list at eval time; a module the graph uses and the lockfile lacks fails with attribute '"<module>@<version>"' missing |
| Build time | Lockfile consistency | Default, with lockfile | link-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.