Go Toolchains and gomod2nix

Updated

September 12, 2026

Go Toolchains and gomod2nix

buildGoModule is Cargo’s buildRustPackage. gomod2nix is closer to Crane: it generates Nix from go.mod so module FODs are per-crate, not one giant vendorHash. The boring default is still buildGoModule until a repo’s vendor FOD is painfully slow; then gomod2nix.

Pin the compiler with buildGoModule.override { go = pkgs.go_1_24; } or an overlay. go in nixpkgs 26.05 is the toolchain. dream2nix analogues exist and are not the desk default.

Mental model

Tool Like (Rust) When
buildGoModule + vendorHash buildRustPackage + cargoHash Default
gomod2nix Crane Many modules, incremental FODs
go overlay / .override { go = … } fenix / rust-overlay Pin compiler version
vendor/ in git cargo vendor in-tree Air-gap; last resort
go.mod  ──gomod2nix──►  gomod2nix.toml + buildGoApplication
         ──buildGoModule──►  one vendorHash FOD

One compiler version per flake. CI, laptop, and buildGoModule share it.

Worked examples

Case 1: Stay on buildGoModule

If nix-build of the API is seconds after a cache hit, stop. gomod2nix is extra files to review.

# desk-api.nix
{ pkgs }:

pkgs.buildGoModule {
  pname = "desk-api";
  version = "1.0.0";
  src = ./.;
  vendorHash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
}

Probe vendorHash. Never leave lib.fakeHash on main.

Case 2: Pin Go like fenix pins rustc

Save as overlay.nix:

# overlay.nix
final: prev: {
  deskGo = prev.go_1_24;
  buildGoModule = prev.buildGoModule.override { go = final.deskGo; };
}

Then pkgs.buildGoModule { … } in that pkgs uses 1.24. Document the version in the flake description. When 26.05’s default go is already what you want, do not overlay.

nix eval -f '<nixpkgs>' go.version

Write that version down. Bump it with the channel, not with go install.

Case 3: gomod2nix generate

nix run github:nix-community/gomod2nix -- --dir .

That writes gomod2nix.toml. Commit it.

Save as flake.nix:

# flake.nix
{
  description = "desk-api via gomod2nix (optional)";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05";
    gomod2nix = {
      url = "github:nix-community/gomod2nix";
      inputs.nixpkgs.follows = "nixpkgs";
    };
  };

  outputs = { self, nixpkgs, gomod2nix }:
    let
      system = "x86_64-linux";
      pkgs = nixpkgs.legacyPackages.${system};
    in
    {
      packages.${system}.desk-api =
        gomod2nix.legacyPackages.${system}.buildGoApplication {
          pname = "desk-api";
          version = "1.0.0";
          src = ./.;
          modules = ./gomod2nix.toml;
        };
    };
}

Exact attr names (buildGoApplication vs mk) follow the gomod2nix README for the lock you pin. follows so you do not eval two nixpkgs.

Case 4: vendor/ in git (air-gap)

go mod vendor
# vendor.nix fragment
{
  src = ./.;
  vendorHash = null;
}

On current buildGoModule, an in-tree vendor/ plus the right flags skips the FOD. It bloats git. Use when the module proxy is forbidden. Prefer vendorHash FODs on the desk.

Case 5: Private modules

The vendor FOD may use the network. Give Nix access-tokens in nix.conf (CI secrets), not a password in the expression.

# do not
{
  GOPROXY = "https://user:hunter2@git.desk.internal";
}
# /etc/nix/nix.conf  (CI runner / ops)
access-tokens = git.desk.internal=…

GOPRIVATE=git.desk.internal belongs in the devShell, not as a store string that leaks into nix why-depends.

The trap

The trap is switching to gomod2nix because a blog compared it to Crane, then checking in 400 generated files for a 3-module API. Measure nix-build time first.

The other trap is three Go versions: home.packages = [ go_1_22 ], a devShell go_1_24, and buildGoModule default. One overlay / one override. CI and laptop match.

The boring rule

  • buildGoModule first. gomod2nix when vendor FODs hurt.
  • Pin go with .override { go = pkgs.go_1_24; } (or whatever 26.05 offers), not a random tarball.
  • follows on gomod2nix’s nixpkgs.
  • No dream2nix as the desk default.
  • One compiler version per flake. Tokens in nix.conf, not in Nix strings.

Try this

  1. nix eval -f '<nixpkgs>' go.version on 26.05. Write it down.
  2. Overlay buildGoModule to go_1_24 if that attr exists; strings the binary for go1..
  3. nix run github:nix-community/gomod2nix -- --help.
  4. git grep GOPATH in the flake — should not set a host GOPATH for the build.