NixOS modules
NixOS produces a full system configuration by combining smaller, more isolated and reusable components: Modules. A module is a file containing a Nix expression with a specific structure. It declares options for other modules to define (give a value). It processes them and defines options declared in other modules.[1]
For example, /etc/nixos/configuration.nix
is a module. Most other modules are in nixos/modules
.
Modules were introduced to allow extending NixOS without modifying its source code.[2] They also allow splitting up configuration.nix
, making the system configuration easier to maintain and to reuse.
Structure
Modules have the following syntax:
{
imports = [
# Paths to other modules.
# Compose this module out of smaller ones.
];
options = {
# Option declarations.
# Declare what settings a user of this module can set.
# Usually this includes a global "enable" option which defaults to false.
};
config = {
# Option definitions.
# Define what other settings, services and resources should be active.
# Usually these depend on whether a user of this module chose to "enable" it
# using the "option" above.
# Options for modules imported in "imports" can be set here.
};
}
Function
A module can be turned into a function accepting an attribute set.
{ config, pkgs, ... }:
{
imports = [];
# ...
}
It may require the attribute set to contain:
config
- The configuration of the entire system.
options
- All option declarations refined with all definition and declaration references.
pkgs
- The attribute set extracted from the Nix package collection and enhanced with the
nixpkgs.config
option. modulesPath
- The location of the
module
directory of NixOS.
Imports
Imports are paths to other NixOS modules that should be included in the evaluation of the system configuration. A default set of modules is defined in nixos/modules/module-list.nix
. These don't need to be added in the import list.
Example
{
imports = [
# Paths to other modules.
# They can be relative paths
./otherModule.nix
# Or absolute
/path/to/otherModule.nix
];
}
modulesPath
Some modules use modulesPath
to import NixOS libraries.
For example nixos/modules/virtualisation/digital-ocean-config.nix
:
{ config, pkgs, lib, modulesPath, ... }:
imports = [
(modulesPath + "/profiles/qemu-guest.nix")
(modulesPath + "/virtualisation/digital-ocean-init.nix")
];
The Nix variable modulesPath
is parsed from the environment variable NIX_PATH
.
When NIX_PATH
is empty, Nix can throw the error undefined variable 'modulesPath'
.
NIX_PATH
should look something like this:
$ echo $NIX_PATH | tr : '\n' nixpkgs=/nix/var/nix/profiles/per-user/root/channels/nixos nixos-config=/etc/nixos/configuration.nix /nix/var/nix/profiles/per-user/root/channels
Here, the modulesPath
is /nix/var/nix/profiles/per-user/root/channels
.
When a Nix expression calls import <nixpkgs>
,
Nix will load /nix/var/nix/profiles/per-user/root/channels/nixos
.
Module Imports vs builtins-imports
Beginners often confuse the modules attribute imports = [./module.nix]
here with the Nix builtins function import module.nix
. The first expects a path to a file containing a NixOS module (having the same specific structure we're describing here), while the second loads whatever Nix expression is in that file (no expected structure). See this post.
Declarations (options)
Declarations specify a module's external interfaces.
optionName = mkOption {
# ...
}
They are created with mkOption
, a function accepting a set with following attributes:[3][4]
type
The type of the option. It may be omitted, but that’s not advisable since it may lead to errors that are hard to diagnose. See NixOS:Declaration for more types
default
The default value used if no value is defined by any module. A default is not required; but if a default is not given, then users of the module will have to define the value of the option, otherwise an error will be thrown.
example
An example value that will be shown in the NixOS manual.
description
A textual description of the option, in DocBook format, that will be included in the NixOS manual.
There are also some helper Option functions:
optionName = mkEnableOption "this cool module";
#this is the same as
optionName = mkOption {
type = lib.types.bool;
default = false;
example = true;
description = "Whether to enable this cool module.";
}
Examples
Simple Enable option
If you just want to be able to enable/disable a module you can define an Enable option like this:
{ lib, config, ...}:
let cfg = config.myModule; in {
options = {
myModule.enable = lib.mkEnableOption "Enable Module";
};
config = lib.mkIf cfg.enable {
#config contents
}
}
Shorthand
If your module has no declarations there is a shorthand:
{
imports = [
# Paths to other modules.
];
# Config definitions.
services.othermodule.enable = true;
# ...
# Notice that you can leave out the "config { }" wrapper.
}
Full Example
To see how modules are setup and reuse other modules in practice put hello.nix
in the same folder as your configuration.nix
:
hello.nix
{ lib, pkgs, config, ... }:
with lib;
let
# Shorter name to access final settings a
# user of hello.nix module HAS ACTUALLY SET.
# cfg is a typical convention.
cfg = config.services.hello;
in {
# Declare what settings a user of this "hello.nix" module CAN SET.
options.services.hello = {
enable = mkEnableOption "hello service";
greeter = mkOption {
type = types.str;
default = "world";
};
};
# Define what other settings, services and resources should be active IF
# a user of this "hello.nix" module ENABLED this module
# by setting "services.hello.enable = true;".
config = mkIf cfg.enable {
systemd.services.hello = {
wantedBy = [ "multi-user.target" ];
serviceConfig.ExecStart = "${pkgs.hello}/bin/hello -g'Hello, ${escapeShellArg cfg.greeter}!'";
};
};
}
The other configuration.nix
module can then import this hello.nix
module
and decide to enable it (and optionally set other allowed settings) as follows:
configuration.nix
{
imports = [ ./hello.nix ];
...
services.hello = {
enable = true;
greeter = "Bob";
};
}
Advanced Use Cases
Compatibility Issues with Different Nixpkgs Versions
Module options between Nixpkgs revisions can sometimes change in incompatible ways.
For example, the option services.nginx.virtualHosts.*.port
in nixpkgs-17.03 was replaced by services.nginx.virtualHosts.*.listen
in nixpkgs-17.09. If configuration.nix
has to accommodate both variants, options
can be inspected:
{ options, ... }: {
services.nginx.virtualHosts.somehost = { /* common configuration */ }
// (if builtins.hasAttr "port" (builtins.head options.services.nginx.virtualHosts.type.getSubModules).submodule.options
then { port = 8000; }
else { listen = [ { addr = "0.0.0.0"; port = 8000; } ]; });
}
Abstract imports
To import a module that's stored somewhere (but for which you have neither an absolute nor a relative path), you can use NIX_PATH elements or specialArgs
from nixos/lib/eval-config.nix
.
This is useful for e.g. pulling modules from a git repository without adding it as a channel, or if you just prefer using paths relative to a root you can change (as opposed to the current file, which could move in the future).
let
inherit (import <nixpkgs> {}) writeShellScriptBin fetchgit;
yourModules = fetchgit { ... };
in rec {
nixos = import <nixpkgs/nixos/lib/eval-config.nix> {
modules = [ ./configuration.nix ];
specialArgs.mod = name: "${yourModules}/${name}";
};
/* use NixOS here, e.g. for deployment or building an image */
}
{ config, lib, pkgs, mod, ... }: {
imports = [
(mod "foo.nix")
];
...
}
Using external NixOS modules
Some external modules provide extra functionality to the NixOS module system. You can include these modules, after making them available as a file system path (e.g. through builtins.fetchTarball
, by using imports = [ `path to module`]
in your configuration.nix
.
- Nixsap - allows to run multiple instances of a service without containers.
- musnix - real-time audio in NixOS.
- nixos-mailserver - full-featured mail server module
- X-Truder Nix-profiles - modules for Nix to quickly configure your system based on application profiles.
disabledModules attribute
You can disable modules by using the top-level attribute disabledModules
. This is useful for disabling modules from Nixpkgs or NixOS. This can also be used to override modules that have the same name (Ex: You want to override a module from NixOS with your own implementation). More info
disabledModules
is a top level attribute just like the imports
or options
of the module.
{ config, lib, pkgs, ... }:
{
disabledModules = [ "./helloWorld.nix" ];
imports =
[
./helloWorld.nix
];
#This does nothing because ./helloWorld is disabled
services.helloWorld.enable = true;
}
Under the hood
The following was taken from a comment by Infinisil on Reddit:[5]
A NixOS system is described by a single system derivation. nixos-rebuild
builds this derivation with
$ nix-build '<nixpkgs/nixos>' -A system
and then switches to that system with
$ result/bin/switch-to-configuration
The entrypoint is the file at '<nixpkgs/nixos>' (./default.nix)
, which defines the system
attribute to be the NixOS option system.build.toplevel
. This toplevel
option is the topmost level of the NixOS evaluation and it's what almost all options eventually end up influencing through potentially a number of intermediate options.
As an example:
- The high-level option
services.nginx.enable
uses the lower-level optionsystemd.services.nginx
- Which in turn uses the even-lower-level option
systemd.units."nginx.service"
- Which in turn uses
environment.etc."systemd/system"
- Which then ends up as
result/etc/systemd/system/nginx.service
in the top-level derivation
So high-level options use lower-level ones, eventually ending up at system.build.toplevel
.
How do these options get evaluated though? That's what the NixOS module system does, which lives in the ./lib
directory (in modules.nix
, options.nix
and types.nix
). The module system can even be used without NixOS, allowing you to use it for your own option sets. Here's a simple example of this, whose toplevel
option you can evaluate with
$ nix-instantiate --eval file.nix -A config.toplevel
let
systemModule = { lib, config, ... }: {
options.toplevel = lib.mkOption {
type = lib.types.str;
};
options.enableFoo = lib.mkOption {
type = lib.types.bool;
default = false;
};
config.toplevel = ''
Is foo enabled? ${lib.boolToString config.enableFoo}
'';
};
userModule = {
enableFoo = true;
};
in (import <nixpkgs/lib>).evalModules {
modules = [ systemModule userModule ];
}
The module system itself is rather complex, but here's a short overview. A module evaluation consists of a set of "modules", which can do three things:
- Import other modules (through
imports = [ ./other-module.nix ];
) - Declare options (through
options = { ... };
) - Define option values (through
config = { ... };
, or without the config key as a shorthand if you don't have imports or options)
To do the actual evaluation, there's these rough steps:
- Recursively collect all modules by looking at all
imports
statements - Collect all option declarations (with
options
) of all modules and merge them together if necessary - For each option, evaluate it by collecting all its definitions (with
config
) from all modules and merging them together according to the options type.
Note that the last step is lazy (only the options you need are evaluated) and depends on other options itself (all the ones that influence it).
More complex usages
The examples below contain:
- a child
mkOption
inherits their default from a parentmkOption
- reading default values from neighbouring
mkOption
(s) for conditional defaults - passing in the config, to read the
hostName
from a submodule (email system) - setting default values from attrset (email system)
- generating documentation for custom modules (outside of Nixpkgs). See here
Source:
- https://github.com/nixcloud/nixcloud-webservices/blob/master/modules/services/reverse-proxy/default.nix
- https://github.com/nixcloud/nixcloud-webservices/blob/master/modules/services/reverse-proxy/options.nix
- https://github.com/nixcloud/nixcloud-webservices/blob/master/modules/services/TLS/default.nix
- https://github.com/nixcloud/nixcloud-webservices/blob/master/modules/services/email/nixcloud-email.nix#L114
(sorry, don't have more time to make this into a nice little guide yet, but these links should be pretty good introductions into more advanced module system usages) qknight
Developing modules
To test your module out, you can run the following from a local checkout of Nixpkgs with a copy of a configuration.nix
:
$ nixos-rebuild build-vm --fast -I nixos-config=./configuration.nix -I nixpkgs=.
If you're developing on top of master, this will potentially cause the compilation of lots of packages, since changes on master might not cached on cache.nixos.org yet. To avoid that, you can develop your module on top of the nixos-unstable
channel, tracked by the eponymous branch in https://github.com/NixOS/nixpkgs:
$ git checkout -b mymodule upstream/nixos-unstable
With Flakes
If you're developing a module from Nixpkgs, you can try and follow the directions here: https://github.com/Misterio77/nix-starter-configs/issues/28.
If you want to develop a module from a git repo, you can use --override-input
. For example, if you have an input in your flake called jovian
, you can use
$ nixos-rebuild switch --override-input jovian <path-to-url> --flake <uri>
Of course, it doesn't have to be nixos-rebuild
in particular.
References
See also
- NixOS:extend_NixOS
- NixOS:Properties
- NixOS discourse, "Best resources for learning about the NixOS module system?"
- Debian Config::Model: target configuration upgrades by abstracting the option of the configuration. Each file is a tree structure where leaves are values defined with an interpreted type. The interpreters are defined for each meta-configuration files name
*.conf
. Configuration files does not seems to interact with each other to make consistent configuration. They provide an UI for editing their configuration file.