···5151Provide it as the first attribute in the module:
52525353```nix
5454+# Non-module dependencies (`importApply`)
5555+{ writeScript, runtimeShell }:
5656+5757+# Service module
5458{ lib, config, ... }:
5559{
5660 _class = "service";
···8791 passthru = {
8892 services = {
8993 default = {
9090- imports = [ ./service.nix ];
9494+ imports = [ (lib.modules.importApply ./service.nix { inherit pkgs; }) ];
9195 example.package = finalAttrs.finalPackage;
9296 # ...
9397 };
+3-1
nixos/doc/manual/development/modular-services.md
···85858686## Writing and Reviewing a Modular Service {#modular-service-review}
87878888-Refer to the contributor documentation in [`nixos/README-modular-services.md`](https://github.com/NixOS/nixpkgs/blob/master/nixos/README-modular-services.md).
8888+A typical service module consists of the following:
8989+9090+For more details, refer to the contributor documentation in [`nixos/README-modular-services.md`](https://github.com/NixOS/nixpkgs/blob/master/nixos/README-modular-services.md).
89919092## Portable Service Options {#modular-service-options-portable}
9193
+90
nixos/modules/system/service/README.md
···53535454- **Simple attribute structure**: Unlike `environment.etc`, `configData` uses a simpler structure with just `enable`, `name`, `text`, `source`, and `path` attributes. Complex ownership options were omitted for simplicity and portability.
5555 Per-service user creation is still TBD.
5656+5757+## No `pkgs` module argument
5858+5959+The modular service infrastructure avoids exposing `pkgs` as a module argument to service modules. Instead, derivations and builder functions are provided through lexical closure, making dependency relationships explicit and avoiding uncertainty about where dependencies come from.
6060+6161+### Benefits
6262+6363+- **Explicit dependencies**: Services declare what they need rather than implicitly depending on `pkgs`
6464+- **No interference**: Service modules can be reused in different contexts without assuming a specific `pkgs` instance. An unexpected `pkgs` version is not a failure mode anymore.
6565+- **Clarity**: With fewer ways to do things, there's no ambiguity about where dependencies come from (from the module, not the OS or service manager)
6666+6767+### Implementation
6868+6969+- **Portable layer**: Service modules in `portable/` do not receive `pkgs` as a module argument. Any required derivations must be provided by the caller.
7070+7171+- **Systemd integration**: The `systemd/system.nix` module imports `config-data.nix` as a function, providing `pkgs` in lexical closure:
7272+ ```nix
7373+ (import ../portable/config-data.nix { inherit pkgs; })
7474+ ```
7575+7676+- **Service modules**:
7777+ 1. Should explicitly declare their package dependencies as options rather than using `pkgs` defaults:
7878+ ```nix
7979+ {
8080+ # Bad: uses pkgs module argument
8181+ foo.package = mkOption {
8282+ default = pkgs.python3;
8383+ # ...
8484+ };
8585+ }
8686+ ```
8787+8888+ ```nix
8989+ {
9090+ # Good: caller provides the package
9191+ foo.package = mkOption {
9292+ type = types.package;
9393+ description = "Python package to use";
9494+ defaultText = lib.literalMD "The package that provided this module.";
9595+ };
9696+ }
9797+ ```
9898+9999+ 2. `passthru.services` can still provide a complete module using the package's lexical scope, making the module truly self-contained:
100100+101101+ **Package (`package.nix`):**
102102+ ```nix
103103+ {
104104+ lib,
105105+ writeScript,
106106+ runtimeShell,
107107+ # ... other dependencies
108108+ }:
109109+ stdenv.mkDerivation (finalAttrs: {
110110+ # ... package definition
111111+112112+ passthru.services.default = {
113113+ imports = [
114114+ (lib.modules.importApply ./service.nix {
115115+ inherit writeScript runtimeShell;
116116+ })
117117+ ];
118118+ someService.package = finalAttrs.finalPackage;
119119+ };
120120+ })
121121+ ```
122122+123123+ **Service module (`service.nix`):**
124124+ ```nix
125125+ # Non-module dependencies (importApply)
126126+ { writeScript, runtimeShell }:
127127+128128+ # Service module
129129+ {
130130+ lib,
131131+ config,
132132+ options,
133133+ ...
134134+ }:
135135+ {
136136+ # Service definition using writeScript, runtimeShell from lexical scope
137137+ process.argv = [
138138+ (writeScript "wrapper" ''
139139+ #!${runtimeShell}
140140+ # ... wrapper logic
141141+ '')
142142+ # ... other args
143143+ ];
144144+ }
145145+ ```
···11# Tests in: ../../../../tests/modular-service-etc/test.nix
22+33+# Non-modular context provided by the modular services integration.
44+{ pkgs }:
55+26# Configuration data support for portable services
37# This module provides configData for services, enabling configuration reloading
48# without terminating and restarting the service process.
59{
610 lib,
77- pkgs,
811 ...
912}:
1013let
···33{
44 config,
55 lib,
66- pkgs,
76 ...
87}:
98let
···1615 python-http-server = {
1716 package = mkOption {
1817 type = types.package;
1919- default = pkgs.python3;
2018 description = "Python package to use for the web server";
2119 };
2220···4644 ];
47454846 configData = {
4949- # This should probably just be {} if we were to put this module in production.
5050- "webroot" = lib.mkDefault {
5151- source = pkgs.runCommand "default-webroot" { } ''
5252- mkdir -p $out
5353- cat > $out/index.html << 'EOF'
5454- <!DOCTYPE html>
5555- <html>
5656- <head><title>Python Web Server</title></head>
5757- <body>
5858- <h1>Welcome to the Python Web Server</h1>
5959- <p>Serving from port ${toString config.python-http-server.port}</p>
6060- </body>
6161- </html>
6262- EOF
6363- '';
4747+ "webroot" = {
4848+ # Enable only if directory is set to use this path
4949+ enable = lib.mkDefault (config.python-http-server.directory == config.configData."webroot".path);
6450 };
6551 };
6652 };
+28-2
nixos/tests/modular-service-etc/test.nix
···1212 nodes = {
1313 server =
1414 { pkgs, ... }:
1515+ let
1616+ # Normally the package services.default attribute combines this, but we
1717+ # don't have that, because this is not a production service. Should it be?
1818+ python-http-server = {
1919+ imports = [ ./python-http-server.nix ];
2020+ python-http-server.package = pkgs.python3;
2121+ };
2222+ in
1523 {
1624 system.services.webserver = {
1725 # The python web server is simple enough that it doesn't need a reload signal.
1826 # Other services may need to receive a signal in order to re-read what's in `configData`.
1919- imports = [ ./python-http-server.nix ];
2727+ imports = [ python-http-server ];
2028 python-http-server = {
2129 port = 8080;
2230 };
23313232+ configData = {
3333+ "webroot" = {
3434+ source = pkgs.runCommand "webroot" { } ''
3535+ mkdir -p $out
3636+ cat > $out/index.html << 'EOF'
3737+ <!DOCTYPE html>
3838+ <html>
3939+ <head><title>Python Web Server</title></head>
4040+ <body>
4141+ <h1>Welcome to the Python Web Server</h1>
4242+ <p>Serving from port 8080</p>
4343+ </body>
4444+ </html>
4545+ EOF
4646+ '';
4747+ };
4848+ };
4949+2450 # Add a sub-service
2551 services.api = {
2626- imports = [ ./python-http-server.nix ];
5252+ imports = [ python-http-server ];
2753 python-http-server = {
2854 port = 8081;
2955 };
+2
nixos/tests/php/fpm-modular.nix
···11+# Run with:
22+# nix-build -A nixosTests.php.fpm-modular
13{ lib, php, ... }:
24{
35 name = "php-${php.version}-fpm-modular-nginx-test";