Skip to content

Commit 08bbfcb

Browse files
committed
feat(docs): New article on unified flake modules
1 parent b27add6 commit 08bbfcb

1 file changed

Lines changed: 135 additions & 0 deletions

File tree

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
+++
2+
title = 'Unified flake module: DNS and nginx vhost'
3+
date = '2026-05-31'
4+
draft = false
5+
weight = 60
6+
+++
7+
8+
It is possible to combine multiple infrastructure-as-code domains.
9+
10+
This example consists of a flake-parts module that combines a [deSEC.io][desec] DNS record via the
11+
community-provided [Valodim/terraform-provider-desec][provider] provider, and an importable NixOS
12+
module that configures the matching nginx virtualhost. The webserver will have a virtualhost rule
13+
corresponding to the DNS entry, preventing drift.
14+
15+
[desec]: https://desec.io
16+
[provider]: https://github.com/Valodim/terraform-provider-desec
17+
18+
The entrypoint is a flake:
19+
20+
```nix {file="flake.nix"}
21+
{
22+
inputs = {
23+
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
24+
flake-parts.url = "github:hercules-ci/flake-parts";
25+
terranix.url = "github:terranix/terranix";
26+
};
27+
28+
outputs = inputs@{ flake-parts, ... }:
29+
flake-parts.lib.mkFlake { inherit inputs; } {
30+
imports = [
31+
inputs.terranix.flakeModule
32+
./modules/sites.nix
33+
];
34+
systems = [ "x86_64-linux" ];
35+
36+
sites.www = {
37+
subname = "www";
38+
domain = "example.dedyn.io";
39+
ipv4 = "203.0.113.42";
40+
};
41+
};
42+
}
43+
```
44+
45+
It mentions a custom configuration option, `sites.<name>` defined in the following module:
46+
47+
```nix {file="modules/sites.nix"}
48+
{ lib, config, ... }:
49+
let
50+
cfg = config.sites;
51+
in
52+
{
53+
options.sites = lib.mkOption {
54+
default = { };
55+
type = lib.types.attrsOf (lib.types.submodule {
56+
options = {
57+
subname = lib.mkOption { type = lib.types.str; };
58+
domain = lib.mkOption { type = lib.types.str; };
59+
ipv4 = lib.mkOption { type = lib.types.str; };
60+
};
61+
});
62+
};
63+
64+
config = {
65+
# terranix configuration: one per site, deSEC DNS A record.
66+
perSystem = { ... }: {
67+
terranix.terranixConfigurations = lib.mapAttrs (name: site: {
68+
modules = [{
69+
terraform.required_providers.desec.source = "Valodim/desec";
70+
71+
variable.desec_token.sensitive = true;
72+
provider.desec.api_token = "\${var.desec_token}";
73+
74+
resource.desec_rrset.${name} = {
75+
domain = site.domain;
76+
subname = site.subname;
77+
type = "A";
78+
ttl = 3600;
79+
records = [ site.ipv4 ];
80+
};
81+
}];
82+
}) cfg;
83+
};
84+
85+
# NixOS module: importable from any nixosSystem, configures nginx.
86+
flake.nixosModules = lib.mapAttrs (name: site: { ... }: {
87+
services.nginx = {
88+
enable = true;
89+
virtualHosts."${site.subname}.${site.domain}" = {
90+
enableACME = true;
91+
forceSSL = true;
92+
locations."/".return = ''200 "hello from ${site.subname}.${site.domain}\n"'';
93+
};
94+
};
95+
networking.firewall.allowedTCPPorts = [ 80 443 ];
96+
}) cfg;
97+
};
98+
}
99+
```
100+
101+
## Use it
102+
103+
Provision the DNS record (the deSEC token is read from the environment):
104+
105+
```shell
106+
$ export TF_VAR_desec_token=""
107+
$ nix run .#www
108+
```
109+
110+
Import the matching NixOS module into the host that should serve the site:
111+
112+
```nix {filename="myhost/configuration.nix"}
113+
{ inputs, ... }: {
114+
imports = [ inputs.self.nixosModules.www ];
115+
}
116+
```
117+
118+
Adding a second site is two more lines in `flake.nix`:
119+
120+
```nix
121+
sites.api = {
122+
subname = "api";
123+
domain = "example.dedyn.io";
124+
ipv4 = "203.0.113.42";
125+
};
126+
```
127+
128+
Both `nix run .#api` and `inputs.self.nixosModules.api` appear automatically.
129+
130+
Their deployment is still handled as two separate steps.
131+
132+
> [!INFO]
133+
> The `Valodim/desec` provider is not on the public Terraform registry; consult
134+
> the provider [README][provider] for installing it (Nix users typically pin
135+
> it via `terraform-providers` overrides or a local plugin cache).

0 commit comments

Comments
 (0)