Skip to content

purpleclay/go-overlay

go-overlay logo

go-overlay

A complete Go development environment for Nix.
Toolchains, tools, and builders — pure, reproducible, and auto-updated.

Nix Go MIT Go Update

  • 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
cd my-app && nix develop

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
cd my-monorepo && nix develop

Onboard an existing project

1. Add the flake input

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? See the traditional Nix installation guide.

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.

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

Build or run any example directly:

# pattern: nix build .#example-<name>
nix build .#example-cobra-cli
nix run .#example-cobra-cli

Go Tools

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-v2.mdgovendor.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.

About

A complete Go development environment for Nix. Toolchains, tools, and builders — pure, reproducible, and auto-updated.

Topics

Resources

License

Code of conduct

Contributing

Stars

Watchers

Forks

Contributors