- 100+ Go versions — from 1.17 to latest, including release candidates, updated within 4 hours of a new release on go.dev.
- No stale
vendorHash — dependencies are pinned per-module with NAR hashes. Change a dep, re-run govendor. No hash archaeology.
- Workspace support — build multi-module
go.work monorepos reproducibly. Neither buildGoModule nor gomod2nix can do this.
- Go tools pinned to your toolchain — govulncheck, gopls, golangci-lint, and more, updated within 6 hours of release and version-locked to your selected Go version with a clear error if incompatible.
- Private modules — standard Go authentication via
.netrc, no custom infrastructure required.
Getting Started
Try Go without installing anything
nix run github:purpleclay/go-overlay -- version
# go version go1.26.2 linux/amd64
Create a new project
The template bootstraps a new project with a dev shell, builder, and drift detection pre-configured:
nix flake new -t github:purpleclay/go-overlay my-app
Create a workspace project
The workspace template bootstraps a minimal multi-module monorepo with a dev shell, builder, and drift detection pre-configured:
nix flake new -t github:purpleclay/go-overlay#workspace my-monorepo
Create a container image project
The image template bootstraps a Go application pre-configured to produce a buildLayeredImage container image alongside the binary:
nix flake new -t github:purpleclay/go-overlay#image my-app
Onboard an existing project
Add go-overlay to your flake.nix inputs and apply the overlay:
{
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
go-overlay.url = "github:purpleclay/go-overlay";
};
outputs = { nixpkgs, flake-utils, go-overlay, ... }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = import nixpkgs {
inherit system;
overlays = [ go-overlay.overlays.default ];
};
in { ... }
);
}
[!TIP]
Not using flakes? default.nix is an overlay. See the traditional Nix installation guide for more.
2. Add govendor to your dev shell
govendor generates and maintains the dependency manifest used during builds:
devShells.default = pkgs.mkShell {
buildInputs = [
go-overlay.packages.${system}.govendor
];
};
3. Generate and commit the manifest
govendor
This creates a govendor.toml with NAR hashes for all dependencies. Commit it to your repository.
[!IMPORTANT]
Re-run govendor whenever you add, remove, or upgrade a dependency. Use govendor --check in CI to catch drift before it reaches production.
govendor exits with a distinct code depending on the outcome, so CI can tell drift apart from a hard failure:
| Code |
Meaning |
0 |
all manifests up to date / generated |
1 |
drift or missing manifest detected (--check) |
2 |
execution error (toolchain failure, parse error, bad flags) |
A run across multiple paths reports the most severe code.
[!WARNING]
Automation that previously treated any non-zero exit as a single failure mode should
now branch on 1 (drift/missing manifest) versus 2 (execution error).
4. Build
Create a default.nix file to define your package:
{ pkgs, go }:
pkgs.buildGoApplication {
inherit go;
pname = "my-app";
version = "0.1.0";
src = ./.;
modules = ./govendor.toml;
}
Then wire it into your flake.nix outputs:
let
go = pkgs.go-bin.fromGoMod ./go.mod;
in {
packages.default = pkgs.callPackage ./default.nix { inherit go; };
}
[!TIP]
fromGoMod auto-selects the Go version from your go.mod. For other version selection options, see the reference guide.
And then build it:
nix build
That's it. No stale vendorHash to fix after every dependency change. 👋
Examples
Not sure how to configure a specific feature? Each example is a self-contained, buildable project — find the pattern you need and use it as a starting point.
| Example |
Features |
| hello-world |
stdlib-only, no manifest |
| http-chi-server |
External deps, modules |
| cobra-cli |
ldflags, subPackages, doCheck, version injection |
| cross-compile |
GOOS, GOARCH, CGO_ENABLED |
| build-tags |
tags parameter |
| local-replaces |
Local replace directives, localReplaces |
| oapi-codegen |
tool directive, preBuild code generation |
| sqlc-codegen |
nativeBuildInputs, preBuild code generation |
| vendored |
Committed vendor/ directory |
| go-workspace |
buildGoWorkspace, go.work, subPackages |
| go-workspace-inferred |
buildGoWorkspace, inferred go.work |
| go-workspace-vendored |
buildGoWorkspace, committed vendor/ |
| wasm-build |
mkVendorEnv + stdenv.mkDerivation |
| nixpkgs-build-go-module |
buildGoModule.override with go-overlay toolchain |
[!TIP]
oapi-codegen and sqlc-codegen demonstrate the two ways to provide a build-time code generation tool — a go.mod tool directive versus nativeBuildInputs. See tool directive vs nativeBuildInputs for which to pick.
Build or run any example directly:
# pattern: nix build .#example-<name>
nix build .#example-cobra-cli
nix run .#example-cobra-cli
Every Go toolchain derivation includes version-locked tools — gopls, golangci-lint, govulncheck, delve, and more — pinned to your selected Go version and updated within 6 hours of a new release.
Add them all to your dev shell with a single attribute:
let
go = pkgs.go-bin.fromGoMod ./go.mod;
in {
devShells.default = pkgs.mkShell {
buildInputs = [
go.withDefaultTools
];
};
}
For selecting specific tools or pinning versions, see the Go Tools reference.
Detecting Drift with Git Hooks
Use cachix/git-hooks.nix to check for manifest drift on commit:
let
pre-commit-check = git-hooks.lib.${system}.run {
src = ./.;
hooks.govendor = {
enable = true;
name = "govendor";
entry = "${go-overlay.packages.${system}.govendor}/bin/govendor --check";
files = "(^|/)go\\.(mod|work)$";
pass_filenames = true;
};
};
in {
devShells.default = pkgs.mkShell {
inherit (pre-commit-check) shellHook;
buildInputs = pre-commit-check.enabledPackages;
};
}
Private Go Modules
Private modules require two things: bypassing the public proxy, and credentials to authenticate with the private host. Set GOPRIVATE to route around the proxy and netrcFile to provide credentials:
[!TIP]
GOPRIVATE implicitly sets GONOPROXY and GONOSUMDB — you only need to set all three explicitly if you require different values for each.
{ pkgs, go }:
pkgs.buildGoApplication {
inherit go;
pname = "myapp";
version = "1.0.0";
src = ./.;
modules = ./govendor.toml;
netrcFile = "${builtins.getEnv "HOME"}/.netrc";
GOPRIVATE = "<your-private-host>/*";
}
[!NOTE]
builtins.getEnv "HOME" reads the host environment to locate ~/.netrc — this is why --impure is required. The file contents are read at eval time and passed into the build sandbox. Credentials are not stored in the repo. If you prefer to keep a .netrc inside the source root, consider encrypting it with git-crypt or sops-nix.
nix build --impure
[!CAUTION]
The .netrc contents are embedded in the derivation, which is stored in the Nix store. The Nix store is world-readable by default.
Further Reading
- reference.md — Full option tables for all builder functions, library functions, and traditional Nix installation.
- govendor-toml-v3.md —
govendor.toml schema reference.
- migrating.md — Migration guides from gomod2nix and buildGoModule.
The go-overlay logo was generated using Google Gemini. The Go gopher was originally designed by Renee French and is licensed under CC BY 4.0. The Nix snowflake is a trademark of the NixOS Foundation. This project is not affiliated with the Go project, the NixOS Foundation, or Google.