buildGoModule is the standard builder for Go projects in nixpkgs. It wraps
stdenv.mkDerivation with Go-specific build phases and produces two
derivations: a fixed-output derivation (FOD) that vendors/downloads
dependencies, and a main derivation that compiles the Go binaries.
The older buildGoPackage (GOPATH-mode) was removed in October 2024. This
document covers only buildGoModule.
| File | Role |
|---|---|
pkgs/build-support/go/module.nix |
The entire buildGoModule implementation (413 lines) |
pkgs/top-level/all-packages.nix:7806-7828 |
Wires versioned Go compilers to versioned builders |
lib/customisation.nix:860-902 |
extendMkDerivation -- the abstraction buildGoModule is built on |
lib/systems/default.nix:548-574 |
Maps Nix platform names to GOOS/GOARCH/GOARM |
pkgs/development/compilers/go/1.24.nix |
Go 1.24 compiler |
pkgs/development/compilers/go/1.25.nix |
Go 1.25 compiler (current default) |
pkgs/development/compilers/go/1.26.nix |
Go 1.26 compiler (latest) |
pkgs/development/compilers/go/go_no_vendor_checks-1.23.patch |
Nix-specific patch to bypass vendor consistency checks |
pkgs/development/compilers/go/go-env-go_ldso.patch |
Nix-specific patch making the ELF interpreter configurable via GO_LDSO |
doc/languages-frameworks/go.section.md |
User-facing documentation |
In pkgs/top-level/all-packages.nix:7806-7828:
go = go_1_25;
buildGoModule = buildGo125Module;
go_latest = go_1_26;
buildGoLatestModule = buildGo126Module;
buildGo124Module = callPackage ../build-support/go/module.nix { go = buildPackages.go_1_24; };
buildGo125Module = callPackage ../build-support/go/module.nix { go = buildPackages.go_1_25; };
buildGo126Module = callPackage ../build-support/go/module.nix { go = buildPackages.go_1_26; };Every versioned builder uses the same module.nix -- the only difference is
which Go compiler is injected. The go comes from buildPackages (the build
platform), which is critical for cross-compilation.
buildGoModule is not a simple wrapper function. It uses lib.extendMkDerivation
(lib/customisation.nix:860-902) to create a proper extension of
stdenv.mkDerivation that:
- Preserves
finalAttrsfixed-point semantics (so attributes can reference each other andoverrideAttrsworks correctly) - Extends the derivation arguments with Go-specific logic
- Excludes internal attribute names (like
overrideModAttrs) from being passed to the underlyingmkDerivation
lib.extendMkDerivation {
constructDrv = stdenv.mkDerivation;
excludeDrvArgNames = [ "overrideModAttrs" ];
extendDrvArgs = finalAttrs: { ... }@args: { ... };
}The extendDrvArgs function receives finalAttrs (the self-referencing fixed
point) and the user-provided arguments, then returns the actual derivation
attributes with all the Go build phases defined inline as Nix strings.
┌──────────────────────┐
│ User's package │
│ (buildGoModule) │
└──────────┬───────────┘
│
┌────────────────┴────────────────┐
│ │
┌──────────▼──────────┐ ┌──────────▼──────────┐
│ goModules (FOD) │ │ Main derivation │
│ │ │ │
│ - Fetches deps │ ─────────▶ - Compiles Go code │
│ - vendorHash pins │ output │ - Runs tests │
│ the output │ is used │ - Installs binaries │
│ - Network access OK │ as input │ - No network access │
└─────────────────────┘ └─────────────────────┘
Nix builds are sandboxed with no network access. Go modules need to be
downloaded from the internet. The solution is a fixed-output derivation
(FOD) whose content is pinned by vendorHash. Because the output hash is known
in advance, Nix allows network access during its build. The main derivation then
uses these pre-fetched dependencies with GOPROXY=off.
These are the Go-specific attributes accepted by buildGoModule
(module.nix:17-70):
| Attribute | Default | Purpose |
|---|---|---|
vendorHash |
required | SRI hash of the vendored dependencies FOD. Set to null if deps are already vendored in source. |
modRoot |
"./" |
Directory containing go.mod/go.sum relative to src |
proxyVendor |
false |
Use go mod download + module proxy instead of go mod vendor |
deleteVendor |
false |
Delete existing vendor/ before fetching deps |
goSum |
null |
Track go.sum changes to trigger rebuilds of goModules |
allowGoReference |
false |
Allow the output to reference the Go compiler in the store |
ldflags |
[] |
Go linker flags (e.g., -s -w, -X main.version=...) |
GOFLAGS |
[] |
Additional Go build flags |
tags |
(none) | Go build tags |
subPackages |
(none) | Specific packages to build (e.g., ["cmd/foo" "cmd/bar"]) |
excludedPackages |
(none) | Packages to exclude from the build |
buildTestBinaries |
false |
Build test binaries instead of regular binaries |
overrideModAttrs |
identity | Function to override the goModules sub-derivation |
modPostBuild |
(none) | postBuild hook for the goModules derivation (not the main one) |
modConfigurePhase |
(none) | Override configurePhase for the goModules derivation |
modBuildPhase |
(none) | Override buildPhase for the goModules derivation |
modInstallPhase |
(none) | Override installPhase for the goModules derivation |
The legacy vendorSha256 is explicitly rejected with an error at module.nix:30-31.
Created only when vendorHash != null. When vendorHash == null, goModules
is set to "" and skipped entirely (the source must already contain a
vendor/ directory or have no external dependencies).
export GOCACHE=$TMPDIR/go-cache
export GOPATH="$TMPDIR/go"
cd "$modRoot"Sets up temporary Go caches and changes to the module root.
The build phase has three stages:
Stage 1 -- Handle existing vendor directory:
- If
deleteVendor = true: removesvendor/, errors if it doesn't exist - If
vendor/still exists: errors with "please setvendorHash = null"
Stage 2 -- Fetch dependencies (two modes):
proxyVendor = false (default) |
proxyVendor = true |
|---|---|
Runs go mod vendor |
Runs go mod download |
Creates a vendor/ directory with all source code |
Populates $GOPATH/pkg/mod/cache/download |
| Simpler, most common | Needed for C dependencies or case-insensitive filesystem conflicts |
Stage 3: Creates vendor/ directory (mkdir -p) and runs postBuild.
proxyVendor = false |
proxyVendor = true |
|---|---|
cp -r vendor $out |
Removes sumdb/, copies $GOPATH/pkg/mod/cache/download to $out |
Errors if the output is empty (tells user to set vendorHash = null).
outputHashMode = "recursive";
outputHash = finalAttrs.vendorHash;
outputHashAlgo = if finalAttrs.vendorHash == "" then "sha256" else null;
dontFixup = true;The FOD inherits these attributes from the main derivation: src,
prePatch, patches, patchFlags, postPatch, preBuild, sourceRoot,
setSourceRoot, env. This is because patches may modify go.mod/go.sum.
It also has impureEnvVars for proxy settings (GIT_PROXY_COMMAND,
SOCKS_SERVER, GOPROXY) since it needs network access.
Before any phase runs, these environment variables are set in env:
env = {
GOOS = go.GOOS; # e.g., "linux"
GOARCH = go.GOARCH; # e.g., "amd64"
GO111MODULE = "on";
GOTOOLCHAIN = "local";
CGO_ENABLED = go.CGO_ENABLED; # usually "1"
GOFLAGS = ...; # assembled from user flags + defaults
};GOFLAGS assembly (module.nix:227-237):
- User-provided
GOFLAGS -mod=vendor(unlessproxyVendor = true)-trimpath(unlessallowGoReference = true)
Warnings are emitted if the user manually sets -mod= or -trimpath.
ldflags (module.nix:243): -buildid= is appended automatically for
reproducibility unless the user already set one.
export GOCACHE=$TMPDIR/go-cache
export GOPATH="$TMPDIR/go"
export GOPROXY=off # No network access
export GOSUMDB=off # No checksum database
# Cross-compilation: set dynamic linker
if [ -f "$NIX_CC_FOR_TARGET/nix-support/dynamic-linker" ]; then
export GO_LDSO=$(cat $NIX_CC_FOR_TARGET/nix-support/dynamic-linker)
fi
cd "$modRoot"Then, if vendorHash != null (goModules was built):
proxyVendor = false |
proxyVendor = true |
|---|---|
rm -rf vendor && cp -r "$goModules" vendor |
export GOPROXY="file://$goModules" |
This is where the output of the goModules FOD is injected into the build.
This is the most substantial phase. It defines two shell functions and then iterates over packages to build.
Discovers which packages to build:
getGoDirs() {
local type="$1"
if [ -n "$subPackages" ]; then
echo "$subPackages" | sed "s,\(^\| \),\1./,g"
else
find . -type f -name \*$type.go -exec dirname {} \; \
| grep -v "/vendor/" | sort --unique | grep -v "$exclude"
fi
}- If
subPackagesis set: uses exactly those (prefixed with./) - Otherwise: finds all directories with
.gofiles, excludingvendor/,_-prefixed dirs,examples,Godeps,testdata, and anything inexcludedPackages
Core compilation function:
buildGoDir() {
local cmd="$1" dir="$2"
declare -a flags
flags+=(${tags:+-tags=$(concatStringsSep "," tags)})
flags+=(${ldflags:+-ldflags="${ldflags[*]}"})
flags+=("-p" "$NIX_BUILD_CORES")
if [ "$cmd" = "test" ]; then
flags+=(-vet=off)
flags+=($checkFlags)
fi
# Run go $cmd, silently ignore "no Go files" errors
if ! OUT="$(go $cmd "${flags[@]}" $dir 2>&1)"; then
if ! echo "$OUT" | grep -qE '(no( buildable| non-test)?|build constraints exclude all) Go (source )?files'; then
echo "$OUT" >&2
return 1
fi
fi
}for pkg in $(getGoDirs ""); do
# Normal mode:
buildGoDir install "$pkg" # go install
# Or if buildTestBinaries = true:
buildGoDir "test -c -o $GOPATH/bin/" "$pkg" # go test -c
doneWhen cross-compiling, Go places binaries in $GOPATH/bin/${GOOS}_${GOARCH}/.
This block moves them up to $GOPATH/bin/ so the install phase works uniformly.
# Remove -trimpath for tests (tests may reference test assets)
export GOFLAGS=${GOFLAGS//-trimpath/}
for pkg in $(getGoDirs test); do
buildGoDir test "$pkg"
donedoCheckdefaults totrue(unlessbuildTestBinaries = true)- Reuses the same
buildGoDirandgetGoDirsfunctions from the build phase - The
testargument togetGoDirscauses it to find*test.gofiles
mkdir -p $out
dir="$GOPATH/bin"
[ -e "$dir" ] && cp -r $dir $outSimply copies compiled binaries from $GOPATH/bin to $out/bin.
vendorHash = "sha256-..."; vendorHash = "sha256-..."; vendorHash = null;
proxyVendor = false (default) proxyVendor = true (no FOD created)
┌─ goModules FOD ──┐ ┌─ goModules FOD ──┐ Source must have
│ go mod vendor │ │ go mod download │ vendor/ already
│ copies vendor/ │ │ copies mod cache │ or no deps
└────────┬─────────┘ └────────┬─────────┘
│ │
┌─ Main build ─────┐ ┌─ Main build ─────┐ ┌─ Main build ─────┐
│ cp goModules → │ │ GOPROXY=file:// │ │ Uses existing │
│ vendor/ │ │ goModules │ │ vendor/ │
│ GOFLAGS= │ │ (no -mod=vendor) │ │ GOFLAGS= │
│ -mod=vendor │ │ │ │ -mod=vendor │
└──────────────────┘ └──────────────────┘ └──────────────────┘
GOOS and GOARCH are derived from the Nix platform in
lib/systems/default.nix:548-574:
go = {
GOARCH = {
"aarch64" = "arm64";
"arm" = "arm";
"i686" = "386";
"x86_64" = "amd64";
"riscv64" = "riscv64";
"wasm32" = "wasm";
# ... and more
}.${final.parsed.cpu.name} or null;
GOOS = if final.isWasi then "wasip1" else final.parsed.kernel.name;
GOARM = ...; # "5", "6", or "7" from cpu.version
};The Go compiler in nixpkgs carries several patches that make buildGoModule work:
Adds an environment variable GO_NO_VENDOR_CHECKS=1 that bypasses Go's vendor
consistency checks. Without this, Go would reject vendor directories that were
constructed by Nix (copied from the FOD) because they may not exactly match what
go mod vendor would produce on the same machine.
The patch modifies two locations in cmd/go/internal/modload/:
import.go: Allows importing vendored packages not listed inmodules.txtvendor.go: Skips vendor consistency checks when the env var is set andmodules.txtis empty
Makes the ELF interpreter (dynamic linker) configurable via a GO_LDSO
environment variable at link time. Normally Go hardcodes the dynamic linker path
at compiler build time. This patch is essential for cross-compilation in Nix,
where the build and target platforms have different dynamic linkers.
Used in the main configurePhase:
export GO_LDSO=$(cat $NIX_CC_FOR_TARGET/nix-support/dynamic-linker)These patches prepend Nix store paths so Go programs can find system data files
(/etc/protocols, /etc/services, MIME types, timezone data) on NixOS, where
these files don't live at their traditional FHS locations.
disallowedReferences = lib.optional (!finalAttrs.allowGoReference) go;By default (allowGoReference = false), the Go compiler is added as a
disallowed reference. This means Nix will fail the build if the output
binary contains any store path pointing to the Go toolchain. Combined with
-trimpath in GOFLAGS, this ensures Go binaries don't leak build-time paths.
Only set allowGoReference = true for programs tightly coupled with the
compiler (e.g., gopls, delve).
Every buildGoModule derivation exposes passthru.overrideModAttrs to allow
overriding the goModules sub-derivation without affecting the main build:
myPackage.overrideAttrs {
overrideModAttrs = finalAttrs: prevAttrs: {
# Add extra native build inputs only to the goModules FOD
nativeBuildInputs = prevAttrs.nativeBuildInputs ++ [ pkgs.mercurial ];
};
}This is implemented using lib.toExtension (module.nix:402) to canonicalize
the function as an attribute overlay, and composed via lib.composeExtensions
when chaining overrides.
1. Nix evaluates buildGoModule { ... }
│
├─ lib.extendMkDerivation merges Go-specific attrs with user attrs
│
├─ If vendorHash != null:
│ │
│ ├─ 2. Build goModules FOD
│ │ ├─ Apply patches (inherited from main derivation)
│ │ ├─ configurePhase: set GOCACHE, GOPATH, cd modRoot
│ │ ├─ buildPhase: optionally deleteVendor, then:
│ │ │ ├─ proxyVendor=false: go mod vendor
│ │ │ └─ proxyVendor=true: go mod download
│ │ ├─ installPhase: copy vendor/ or mod cache to $out
│ │ └─ Hash output against vendorHash (FOD verification)
│ │
│ └─ 3. Build main derivation (goModules is now a store path)
│
└─ If vendorHash == null:
│
└─ 3. Build main derivation (no goModules, source has vendor/)
3. Main derivation
├─ configurePhase:
│ ├─ Set GOCACHE, GOPATH, GOPROXY=off, GOSUMDB=off
│ ├─ Set GO_LDSO for cross-compilation
│ ├─ cd modRoot
│ └─ Inject goModules: copy vendor/ or set GOPROXY=file://
│
├─ buildPhase:
│ ├─ Discover packages (getGoDirs)
│ ├─ Build exclusion pattern
│ └─ For each package: go install (or go test -c)
│ with -tags, -ldflags, -p $NIX_BUILD_CORES
│
├─ checkPhase (if doCheck=true):
│ ├─ Remove -trimpath from GOFLAGS
│ └─ For each test package: go test
│
├─ installPhase:
│ └─ cp $GOPATH/bin → $out/bin
│
└─ fixupPhase (standard):
└─ Verify no disallowed references to Go compiler
buildGoPackage was removed in October 2024. Key differences from buildGoModule:
| Aspect | buildGoModule |
buildGoPackage |
|---|---|---|
| Go module mode | GO111MODULE=on |
GO111MODULE=off (GOPATH mode) |
| Dependencies | go.mod/go.sum + vendorHash |
goDeps Nix expression listing each dependency |
| Source layout | Source stays in place | Source moved into $GOPATH/src/$goPackagePath |
| Required attribute | vendorHash |
goPackagePath |
doCheck default |
true |
false |
| Implementation | lib.extendMkDerivation with finalAttrs |
Direct stdenv.mkDerivation with removeAttrs |