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

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

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? 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.

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-v3.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