Usage
For all the modules options take a look here.
There is a quite elaborate example here, you can also use it as a template (nix flake init -t github:Janik-Haag/nixos-dns
).
classical nix
Please note that the following code uses colmena to retrieve the DNS information from the hosts configurations, and npins for version pinning. You can of course use nixos-dns without colmena, you'll just have to figure out how to reference nixosConfigurations.
# default.nix
let
pins = import ./npins;
in
{ pkgs ? import pins.nixpkgs { }
, colmena ? pins.colmena
, nixos-dns ? import pins.nixos-dns
}:
let
generate = nixos-dns.utils.generate pkgs;
in {
dns = generate.octodnsConfig {
config = {
providers = {
hetzner = {
class = "octodns_hetzner.HetznerProvider";
token = "env/HETZNER_DNS_API";
};
};
};
zones = {
"example.com." = nixos-dns.utils.octodns.generateZoneAttrs [ "hetzner" ];
};
dnsConfig = {
nixosConfigurations = (import "${colmena}/src/nix/hive/eval.nix" {
rawHive = (import ./hive.nix { inherit pkgs nixos-dns; });
}).nodes;
extraConfig = import ./dns.nix;
};
};
}
# hive.nix
{ pkgs, nixos-dns }: {
meta = {
nixpkgs = pkgs;
};
defaults = { pkgs, lib, config, ... }: {
imports = [
((import nixos-dns).nixosModules.default)
];
};
exampleHost = {
deployment.targetHost = "host.example.com";
deployment.tags = [ "infra" ];
imports = [
./hosts/exampleHost.nix
];
};
}
Now you just need to run nix-build -A dns
to realize the zonefiles and octodns config.
flakes
{
# You of course have to add the `nixos-dns` input like:
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
nixos-dns.url = "github:Janik-Haag/nixos-dns";
nixos-dns.inputs.nixpkgs.follows = "nixpkgs";
};
outputs = inputs @ {
self,
nixpkgs,
nixos-dns
}: let
# You probably know this but flake outputs are architecture dependent,
# so we use this little helper function. Many people use `github:numtide/flake-utils` for that.
forAllSystems = nixpkgs.lib.genAttrs [ "x86_64-linux" "aarch64-linux" ];
in {
# Your NixOS configurations
nixosConfigurations = {
exampleHost = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
# Note that we are importing the nixos-dns module here
nixos-dns.nixosModules.dns
./hosts/exampleHost.nix
];
};
};
# We are adding this as package since the zoneFile output is architecture dependent
packages = forAllSystems (system:
let
# `nixos-dns.utils.generate` is a function taking in pkgs as argument
# and returns a set of functions that are architecture dependent like writing zoneFiles
generate = nixos-dns.utils.generate nixpkgs.legacyPackages.${system};
in
# the attrset `generate.zoneFiles` get passed here is the default interface for dnsConfigs with nixos-dns
# and anything from the debugging functions, `generate.octodnsConfig` and zoneFiles uses it.
zoneFiles = generate.zoneFiles {
inherit (self) nixosConfigurations;
extraConfig = import ./dns.nix;
};
}
)
};
}
NixOS Module
NixOS-DNS was built to decouple modules even more from their host.
To achieve this we have the concept of baseDomains
and subDomains
.
In a nixos hosts configuration you would do something like:
networking.hostName = "example-host";
networking.domains = {
enable = true;
baseDomains = {
"example.com" = {
a.data = "203.0.113.42";
aaaa.data = "2001:db8:1c1b:c00e::1";
};
};
};
This enables the networking.domains
module.
And registers the baseDomain example.com
with the a
and aaaa
records set.
You might notice the .data
behind any record, this is because you might want to set the ttl different based on record type.
As you can see above the .ttl
isn't specifically added to every record, this is because there is networking.domains.defaultTTL
So every record
has two fields ttl
and data
, the data type differs based on the record, for more info please refer to the module docs.
And inside of a module you would do something like:
networking.domains.subDomains."grafana.example.com" = { };
So this would produce this set:
{
"example.com" = {
"grafana.example.com" = {
a = {
ttl = 86400;
data = "203.0.113.42";
};
aaaa = {
ttl = 86400;
data = "2001:db8:1c1b:c00e::1";
};
};
};
}
note
baseDomains and their records don't end up in zone files, octodns configs, or any other output for that matter So in the example above for "example.com" to end up in a zone file you would have to add:
networking.domains.subDomains."example.com" = { };
to the hosts configuration.
Nix supports ${}
operations inside of attrsets, so you can get creative and do stuff like:
networking.domains.subDomains."${networking.hostname}.example.com" = { };
networking.domains.subDomains."*.${networking.hostname}.example.com" = { };
networking.domains.subDomains."${networking.hostname}.prometheus.example.com" = { };
NixOS-DNS does a bunch of magic to automatically map subDomains to their closest baseDomain and throws an error if there is no matching baseDomain. So if we have:
networking.domains.baseDomains = {
"example.net" = {};
"example.com" = {};
"domain.example.com" = {};
"subdomain.example.com" = {};
};
and:
networking.domains.baseDomains = {
"example.com" = {};
"mydomain.example.com" = {};
"cats.subdomain.example.com" = {};
};
We would get:
subDomains | matches |
---|---|
"example.com" | "example.com" |
"mydomain.example.com" | "example.com" |
"cats.subdomain.example.com" | "subdomain.example.com" |
And example.net
just wouldn't get matched, but that's fine since it is a baseDomain, if it were a subDomain it would cause an error.
extraConfig
You probably want to add some more information, to do so you can use the extraConfiguration key in the dnsConfig. Please take a look at the example for usage information.
All the hosts in nixosConfigurations
and extraConfig
get merged and nothing gets overwritten.
So if you define multiple domains with the same records all the record data gets merged.
octodns
NixOS-DNS has native octodns support.
To use it add a package like the zoneFiles one above just for octodns using generate.octodnsConfig
which expects a attrSet with a dnsConfig
, a config
and a zones key.
This would look like:
...
octodns = generate.octodnsConfig {
# this is the same attr we pass to zoneFiles
dnsConfig = {
inherit (self) nixosConfigurations;
extraConfig = import ./dns.nix;
};
# the octodns config key
config = {
providers = {
# adding a provider to push to
powerdns = {
class = "octodns_powerdns.PowerDnsProvider";
host = "ns.dns.invalid";
# reads the env var from your shell
api_key = "env/POWERDNS_API_KEY";
};
};
};
zones = {
# `nixos-dns.utils.octodns.generateZoneAttrs` is a helper function
# generating the correct values for usage with NixOS-DNS
# this is the only place in nixos-dns having trailing dots
# this was left in so we have a one to one map of the octodns values
"example.com." = nixos-dns.utils.octodns.generateZoneAttrs [ "powerdns" ];
"example.org." = nixos-dns.utils.octodns.generateZoneAttrs [ "powerdns" ];
"missing.invalid." = nixos-dns.utils.octodns.generateZoneAttrs [ "powerdns" ];
};
};
...
With the example above we at least need the octodns bind and powerdns provider. The powerdns provider is needed because it's used in the example. NixOS-DNS uses the bind provider internally, since it can reads zone-files, so we also need that. We do this because it is a lot less maintenance and less likely to have bugs instead of also maintaining a nixos-dns to octodns internal yaml builder.
You can run the example below to check if your config works, just make sure to do a nix build /your/nixos/config#octodns
before and replace example.com.
(note the trailing dot) with your domain.
nix-shell -p 'octodns.withProviders (ps: [ octodns-providers.bind octodns-providers.powerdns ])' --run "POWERDNS_API_KEY="" octodns-dump --config-file=./result --output-dir=/tmp/octodns_dump example.com. config"
cat /tmp/octodns_dump/example.com.yaml
Please refer to the octodns documentation for more information, the values should map one to one to nixos-dns.
Updating
We want to strongly encourage you to take a look at the CHANGELOG.md before updating. Other then that the updates should be as straight forward as any other NixOS updates.