Using flake output package in existing service

I’m trying to build Caddy with the Tailscale plugin. After a bunch of flailing around I’ve settled on this approach Specifically I have this flake:

{
  inputs = {
    nixpkgs.url = "nixpkgs";
    flake-utils.url = "github:numtide/flake-utils";
    caddy.url = "github:vincentbernat/caddy-nix";
  };
  outputs =
    {
      self,
      nixpkgs,
      flake-utils,
      caddy,
    }:
    flake-utils.lib.eachDefaultSystem (
      system:
      let
        pkgs = import nixpkgs {
          inherit system;
          overlays = [ caddy.overlays.default ];
        };
      in
      {
        packages = {
          default = pkgs.caddy.withPlugins {
            plugins = [ "github.com/tailscale/caddy-tailscale@f21c01b660c896bdd6bacc37178dc00d9af282b4" ];
            hash = "sha256-zrL1wrWXbXnBrWHSnuNaoO2Q7R9GL3/DfUtS5vTqono=";
          };
        };
      }
    );
}

I can run nix build and get what looks like a working package. Now I want to use that from another module like this from roles/caddy.nix:

{
  config,
  system,
  ...
}:

{
  services.caddy = {
    enable = true;
    package = ...; # What here?
    email = "me";
  };
  age.secrets.tsAuthKey = {
    file = ../secrets/ts_auth_key.age;
    owner = config.services.caddy.user;
    group = config.services.caddy.group;
    mode = "600";
  };
  systemd.services.caddy.serviceConfig.EnvironmentFile = config.age.secrets.tsAuthKey.path;
}

How do I reference this package from my main NixOS flake? I have the following:

{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
    nixpkgsUnstable.url = "github:NixOS/nixpkgs/nixos-unstable";
    nixos-hardware.url = "github:NixOS/nixos-hardware/master";
    home-manager = {
      url = "github:nix-community/home-manager/release-24.11";
      inputs.nixpkgs.follows = "nixpkgs";
    };
    agenix.url = "github:ryantm/agenix";
    simple-nixos-mailserver.url = "gitlab:simple-nixos-mailserver/nixos-mailserver/nixos-24.05";
    caddy.url = "path:pkgs/caddy";
  };

  outputs =
    {
      nixpkgs,
      nixpkgsUnstable,
      home-manager,
      nixos-hardware,
      agenix,
      simple-nixos-mailserver,
      caddy,
      ...
    }:
...

Then I have individual hosts with their own hosts/*/default.nix, one of which includes the above caddy.nix. But I’m not clear on how to reference the package, produced by the separate Caddy flake, in this one. I’ve tried something like package = caddy;, importing the flake path, passing it into the module as an argument, etc. Nothing seems to work.

So how do I build a package in a new flake, import it into my main server/infrastructure flake, then reference that built package in the default nixpkgs module definitions so it gets swapped out?

Thanks for any help. Happy to send along more config snippets if needed, I just don’t know what’s relevant and there’s a lot here. :slight_smile:

  1. Name the argset that’s passed to the outputs functon of the consumer flake. Usually people call it inputs but the name doesn’t matter as long as you’re consistent.
  outputs =
    inputs @ {
  1. Pass the binding into your config via the specialArgs arg of lib.nixosSystem:
nixosConfiguratios.FOO = lib.nixosSystem {
  specialArgs = { inherit inputs; }
  # ...
};
  1. Use that module arg in your config
{ pkgs, inputs, ... }:
{
  services.caddy.package = inputs.caddy.packages.${pkgs.stdenv.hostPlatform.system}.default;
}

I’m not 100% familiar with caddy or its module, but that’s usually what I do for other modules.

Hey, first of all thanks for this! Apologies for not circling back for a while–posted it over a hectic/chaotic holiday season, and while it did solve the immediate issue I had, it brought up a few questions for me that I wanted to ask when I had the cycles to write them out.

More specifically, there are a few different inputs/outputs in flake-based configs, and it’s not always clear to me when and where to put a thing so it propagates. Here are some examples from my current config. I’ve also done a lot of copy-paste so maybe I can simplify/streamline some of these?

Flake inputs

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
    nixpkgsUnstable.url = "github:NixOS/nixpkgs/nixos-unstable";
    nixos-hardware.url = "github:NixOS/nixos-hardware/master";
    home-manager = {
      url = "github:nix-community/home-manager/release-24.11";
      inputs.nixpkgs.follows = "nixpkgs";
    };
    agenix.url = "github:ryantm/agenix";
    simple-nixos-mailserver.url = "gitlab:simple-nixos-mailserver/nixos-mailserver/nixos-24.05";
    caddy.url = "path:pkgs/caddy";
  };

I’m guessing these are the dependencies for the entire world as it pertains to this specific config? In this case it’s for a server and a couple laptops, so has a handful of hardware/service-related flakes.

Flake outputs

  outputs =
    inputs@{
      nixpkgs,
      nixpkgsUnstable,
      home-manager,
      nixos-hardware,
      agenix,
      simple-nixos-mailserver,
      ...
    }:
    let
      system = "x86_64-linux";
      overlayUnstable = final: prev: {
        unstable = import nixpkgsUnstable {
          inherit system;
          config.allowUnfree = true;
        };
      };
    in
    {

I’m guessing these are the individual systems, shells, etc. being managed by the flake. And I’m guessing the inputs are passed as inputs.<whatever> and I’m destructuring a few to make them more convenient?

Then, in individual outputs,

        nixbox = nixpkgs.lib.nixosSystem {
          inherit system;
          modules = [
            agenix.nixosModules.default
            {
              environment.systemPackages = [ agenix.packages.${system}.default ];
            }
            (
              { config, pkgs, ... }:
              {
                nixpkgs.overlays = [ overlayUnstable ];
              }
            )
            home-manager.nixosModules.home-manager
            {
              home-manager.useGlobalPkgs = true;
              home-manager.useUserPackages = true;
            }
            ./hosts/nixbox
          ];

There are a couple things going on here, but modules is another kind of input that seems to take both modules, functions, and paths to modules as arguments. To be clear, I’ve read about modules. What I’m struggling with is how all these concepts interconnect with flakes. Where do the values I can include in modules come from relative to flake inputs/outputs?

Module parameters and, maybe, specialArgs?

        thewordnerd = nixpkgs.lib.nixosSystem {
          inherit system;
          specialArgs = { inherit inputs; };
...
# A separate module for a Gitea instance installing from unstable so I get 1.23.1
{ inputs, ... }:

{
  containers.dev = {
    nixpkgs = inputs.nixpkgsUnstable;

Maybe `specialArgs` makes its values available in module arguments specifically? Is that the only way to make a flake output available to modules?

Is there a good explainer making this all clearer? Funneling data from one bit of my config to where I need it is a perpetual pain point for me and I think I need to figure that out next.

Thanks again.

To make this a bit more tangible/actionable, here’s the current exact problem I’m trying to solve. I have a containerized Gitea that’s currently nothing special–it’s just a module I load into my config and go. Now I’m trying to use unstable in the container only because I want to run Gitea 1.23.1 in the container but stable on the host. I don’t particularly care whether the container as a whole runs unstable, or just installs the unstable package, though I think I’d prefer the latter if possible. So here’s my attempt to thread the needle:

{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
    nixpkgsUnstable.url = "github:NixOS/nixpkgs/nixos-unstable";
...
  outputs =
    inputs@{
      nixpkgs,
      nixpkgsUnstable,
      home-manager,
      nixos-hardware,
      agenix,
      simple-nixos-mailserver,
      ...
    }:
    let
      system = "x86_64-linux";
      overlayUnstable = final: prev: {
        unstable = import nixpkgsUnstable {
          inherit system;
          config.allowUnfree = true;
        };
      };
    in
    {
      nixosConfigurations = {
...
        thewordnerd = nixpkgs.lib.nixosSystem {
          inherit system;
          specialArgs = { inherit inputs; };
          modules = [
            agenix.nixosModules.default
            {
              environment.systemPackages = [ agenix.packages.${system}.default ];
            }
            (
              { config, pkgs, ... }:
              {
                nixpkgs.overlays = [ overlayUnstable ];
              }
            )
            home-manager.nixosModules.home-manager
            {
              home-manager.useGlobalPkgs = true;
              home-manager.useUserPackages = true;
            }
            simple-nixos-mailserver.nixosModule
            ./hosts/hub
          ];
...
# ./hosts/hub/default.nix
{ config, ... }:

{
  imports = [
    ./hardware-configuration.nix
...
    ./apps/dev.nix
  ];
...
# ./apps/dev.nix
{ inputs, ... }:

{
  services = {
    postgresql = {
      ensureDatabases = [ "dev" ];
      ensureUsers = [
        {
          name = "dev";
          ensureDBOwnership = true;
        }
      ];
    };

  containers.dev = {
# And what goes *here* is a very good question.

I can and have tried a couple things here with mixed results:

nixpkgs = inputs.nixpkgsUnstable; on the container definition: This seems like it might be building all of nixpkgs. In particular I see it doing things with ghc and other large packages, and I don’t think those are even transitive deps of Gitea.

package = <something>; in the container’s service config for gitea: I can’t seem to reference inputs or anything here. Here’s what I’ve tried specifically:

  containers.dev = {
    autoStart = true;
    privateNetwork = true;
    hostAddress = "192.168.0.1";
    localAddress = "192.168.0.2";
    config =
      {
        config,
        pkgs,
        inputs,
        lib,
        ...
      }:
      {
        services.gitea = {
package = inputs.pkgs.gitea;

error: attribute 'inputs' missing

In short, I suck at threading these Nix needles and don’t get what I’m missing here. Thanks again for any help.