Build Caddy with modules in devenv shell

Hi there!

I’ve been moving my development environments from being built in and around docker, to nix shells built with devenv. Everything’s working out great so far, but the only thing I’m having difficulty with is building a custom caddy binary with certain modules enabled.

In the docker world I’d put something like this in my Dockerfile:

RUN xcaddy build \
	--with github.com/dunglas/mercure \
	--with github.com/dunglas/mercure/caddy

The equivalent of doing the same in Nix would be to build an overlay. I’ve kinda figured out what that overlay should look like (found at: xcaddy: init at 0.3.1 by tjni · Pull Request #191883 · NixOS/nixpkgs · GitHub)

final: prev:
let
  plugins = [
    "github.com/dunglas/mercure@v0.14.4"
    "github.com/dunglas/mercure/caddy@ddcf0045223c6f3fcc8be21fba3414bbbf2af730"
  ];
in
with final;
{
  caddy = stdenv.mkDerivation rec {
    pname = "caddy";
    version = "2.5.2";
    dontUnpack = true;

    nativeBuildInputs = [ git go xcaddy ];

    buildPhase = let
      pluginArgs = lib.concatMapStringsSep " " (plugin: "--with ${plugin}") plugins;
    in ''
      runHook preBuild

      ${xcaddy}/bin/xcaddy build "v${version}" ${pluginArgs}

      runHook postBuild
    '';

    installPhase = ''
      runHook preInstall

      mkdir -p $out/bin
      mv caddy $out/bin

      runHook postInstall
    '';
  };
}

But… Where do I put it? Putting it in a devenv.nix file won’t work, as that’s not a flake, so I think I have two options:

  1. Convert devenv.nix into a flake.nix
  2. Start my own caddy repository, add a flake file and add that one as input, so that I can use that caddy package instead of the default one

I think I like option two a bit more, as it’s easier to reuse in various situations and also, more in line with the nix philosophy, but it feels kind of silly to start a repository with basically a flake.nix and flake.lock file.

Does anyone have any pointers on how to proceed? Any help would be greatly appreciated :smiley:

Unfortunately I don’t think devenv allows you to overwrite the pkgs argument globally without calling it from a flake.nix, but you should be able to locally extend the pkgs with your overlay like so

# devenv.nix
{pkgs, ...}: 
let extendedPkgs = pkgs.extend (final: prev: {/* your overlay */});
in {
  # Your devenv stuff
  # ...
  services.caddy.package = extendedPkgs.caddy;
  # Optionally
  packages = [ extendedPkgs.caddy ];
}

although you wouldn’t really need an overlay for that.
This should work too

# devenv.nix
{pkgs, ...}:
let myCaddy = with pkgs; stdenv.mkDerivation rec {/* your caddy definition */};
in {
  # Your devenv stuff
  # ...
  services.caddy.package = myCaddy;
  # Optionally
  packages = [ myCaddy ];
}

Hey! You don’t really need an overlay for this, you can just do like @BurNiinTRee showed:

{
  services.caddy.package = pkgs.callPackage ./caddy.nix {};
}

Ideally, you could contribute a plugins attribute to xcaddy in nixpkgs and just override it.

Oh wow, this turned out to be a lot simpler than I anticipated :smiley: Thanks so much!

I ended up with the following devenv (left out the irrelevant parts), just in case someone else is thinking about doing the same thing:

{ pkgs, config, ... }:

let
    caddy = with pkgs; stdenv.mkDerivation rec {
        pname = "caddy";
        version = "2.6.2";
        dontUnpack = true;

        nativeBuildInputs = [ git go xcaddy ];

        plugins = [
            "github.com/dunglas/mercure@v0.14.4"
            "github.com/dunglas/mercure/caddy@ddcf0045223c6f3fcc8be21fba3414bbbf2af730"
        ];

        configurePhase = ''
            export GOCACHE=$TMPDIR/go-cache
            export GOPATH="$TMPDIR/go"
        '';

        buildPhase = let
          pluginArgs = lib.concatMapStringsSep " " (plugin: "--with ${plugin}") plugins;
        in ''
          runHook preBuild
          ${xcaddy}/bin/xcaddy build "v${version}" ${pluginArgs}
          runHook postBuild
        '';

        installPhase = ''
          runHook preInstall
          mkdir -p $out/bin
          mv caddy $out/bin
          runHook postInstall
        '';
    };
in
{

    services.caddy.package = caddy;
    services.caddy.enable = true;
    services.caddy.config = ''
        {
            debug
        }

        customcaddy.test

        tls ./etc/customcaddy.test.pem ./etc/customcaddy.test-key.pem

        route {
            root * ./public
            mercure {
                # Transport to use (default to Bolt)
                transport_url {$MERCURE_TRANSPORT_URL:bolt:///data/mercure.db}
                # Publisher JWT key
                publisher_jwt {env.MERCURE_PUBLISHER_JWT_KEY} {env.MERCURE_PUBLISHER_JWT_ALG}
                # Subscriber JWT key
                subscriber_jwt {env.MERCURE_SUBSCRIBER_JWT_KEY} {env.MERCURE_SUBSCRIBER_JWT_ALG}
                # Allow anonymous subscribers (double-check that it's what you want)
                anonymous
                # Enable the subscription API (double-check that it's what you want)
                subscriptions
                # Extra directives
                {$MERCURE_EXTRA_DIRECTIVES}
            }
        }
    '';
}

I do think I need to brush up on my nix skills though :sweat_smile:

1 Like

Hi,

I try to build caddy with Vulcain plugin with your code example.

I got this error at build stage:

go: github.com/caddyserver/caddy/v2@v2.6.4: Get "https://proxy.golang.org/github.com/caddyserver/caddy/v2/@v/v2.6.4.info": dial tcp: lookup proxy.golang.org on [::1]:53: read udp [::1]:51839->[::1]:53: read: connection refused
2023/03/28 16:36:07 [FATAL] exit status 1

Any ideas to solve this issue?

Nix sandbox is preventing the build from accessing the network (for reproducibility) How to run `nix-build` with sandbox = false?
nix.settings.trusted-users

1 Like

Thx,

I was able to build caddy with plugins with following command
sudo nixos-rebuild build --option sandbox false