Skip to content

caddy-override-keep-withplugins

Overlays

Rebuild Caddy against a different Go toolchain (a newer/pinned release, a patched compiler, whatever) without losing caddy.withPlugins.

The problem

You want Caddy built with a specific Go. The obvious move works:

prev.caddy.override { buildGoModule = prev.buildGoModule.override { go = myGo; }; }

...but the resulting caddy no longer has passthru.withPlugins. Any downstream code that does caddy.withPlugins { plugins = [ ... ]; hash = ...; } now fails with attribute 'withPlugins' missing.

Why it happens

withPlugins is a helper nixpkgs attaches to Caddy via passthru, defined in pkgs/by-name/ca/caddy/plugins.nix. Two things break it:

  1. overrideAttrs replaces passthru instead of deep-merging, so if you overrideAttrs the base package the plugin builder is gone.
  2. Even the original withPlugins closed over the original caddy and Go. If you preserved it verbatim, plugin builds would use the wrong toolchain — not the one you just overrode to.

So there's no way to "keep" the old withPlugins; you have to re-create it, bound to your overridden inputs.

The fix

callPackage nixpkgs' own plugins.nix, explicitly passing the overridden caddy and go, and merge it back onto passthru:

caddy = caddyBase.overrideAttrs (old: {
  passthru = (old.passthru or { }) // {
    withPlugins = final.callPackage
      "${prev.path}/pkgs/by-name/ca/caddy/plugins.nix"
      { caddy = final.caddy; go = goToolchain; };
  };
});

Key points:

  • final.callPackage + caddy = final.caddy — refer to the final (fully-overlaid) caddy so withPlugins builds the same package you're exporting, including later overlays.
  • ${prev.path}/pkgs/by-name/... — reuse upstream's plugins.nix from the nixpkgs source tree (prev.path) rather than vendoring a copy that will rot.
  • (old.passthru or { }) // { ... } — merge, don't clobber, so any other passthru attributes survive.

Usage

Add default.nix to nixpkgs.overlays. Edit the goToolchain binding to select your Go:

  • Reuse an existing one: goToolchain = prev.go_1_23;
  • Pin a specific upstream release: override version + src.hash as shown in default.nix (get the hash with nix-prefetch-url on the go<version>.src.tar.gz tarball).

After the overlay, both pkgs.caddy and pkgs.caddy.withPlugins { ... } build against your toolchain.

Caveats

  • If the pinned Go version's plugins.nix interface changes upstream, this may need adjusting — it depends on plugins.nix accepting caddy and go arguments (true for current nixpkgs).
  • Pinning Go by version+src forces a full toolchain rebuild; expect a long first build.

Source

overlays/caddy-override-keep-withplugins/default.nix
# Overlay: rebuild Caddy against a different Go toolchain WITHOUT losing
# `caddy.withPlugins`.
#
# The trap: `caddy.override { buildGoModule = ...; }` followed by
# `overrideAttrs` gives you a Caddy built with your Go, but it silently
# DROPS `passthru.withPlugins` — the helper nixpkgs exposes to build Caddy
# with extra plugins. `overrideAttrs` replaces `passthru` rather than
# deep-merging the plugin builder back in, and even if you preserve the old
# `passthru`, the stale `withPlugins` still closes over the *original*
# caddy/go, so plugin builds don't pick up your override.
#
# The fix: re-create `withPlugins` by `callPackage`-ing nixpkgs' own
# `pkgs/by-name/ca/caddy/plugins.nix`, explicitly bound to the overridden
# `caddy` and `go`. Then `caddy.withPlugins { plugins = [ ... ]; hash = ...; }`
# builds again, against your toolchain.
#
# Usage: add to `nixpkgs.overlays`. Parameterize the Go package and (optionally)
# a pinned Go version+src via the `let` bindings below.

final: prev:
let
  # --- Pick the Go toolchain Caddy should build against. --------------------
  #
  # Simplest form: just reuse an existing Go from nixpkgs, e.g.
  #     go = prev.go_1_23;
  #
  # This example pins a specific upstream Go release by overriding version+src.
  # Replace version and hash with the release you need (hash is the sha256 of
  # the go<version>.src.tar.gz tarball; get it with `nix-prefetch-url`).
  goToolchain = prev.go.overrideAttrs (_: rec {
    version = "1.26.2";
    src = prev.fetchurl {
      url = "https://go.dev/dl/go${version}.src.tar.gz";
      hash = "sha256-LpHrtpR6lulDb7KzkmqIAu/mOm03Xf/sT4Kqnb1v1Ds=";
    };
  });

  # buildGoModule bound to the chosen toolchain.
  buildGoModule' = prev.buildGoModule.override { go = goToolchain; };

  # Caddy built with the chosen toolchain. `.override` reaches the package's
  # `buildGoModule` argument; this alone still loses `withPlugins`.
  caddyBase = prev.caddy.override { buildGoModule = buildGoModule'; };
in
{
  caddy = caddyBase.overrideAttrs (old: {
    # Merge (don't replace) passthru, then re-bind withPlugins to the
    # overridden caddy + go so plugin builds use the same toolchain.
    passthru = (old.passthru or { }) // {
      # `prev.path` is the path to the nixpkgs source tree, so this reuses
      # upstream's own plugins.nix rather than vendoring a copy.
      withPlugins = final.callPackage
        "${prev.path}/pkgs/by-name/ca/caddy/plugins.nix"
        {
          caddy = final.caddy; # the overridden caddy (this attr)
          go = goToolchain;
        };
    };
  });
}