Unified flake module: DNS and nginx vhost

Last updated 2026-05-31 · 2 min read · Edit this page on GitHub

It is possible to combine multiple infrastructure-as-code domains.

This example consists of a flake-parts module that combines a deSEC.io DNS record via the community-provided Valodim/terraform-provider-desec provider, and an importable NixOS module that configures the matching nginx virtualhost. The webserver will have a virtualhost rule corresponding to the DNS entry, preventing drift.

The entrypoint is a flake:

nix flake.nix
{
  inputs = {
    nixpkgs.url     = "github:nixos/nixpkgs/nixos-unstable";
    flake-parts.url = "github:hercules-ci/flake-parts";
    terranix.url    = "github:terranix/terranix";
  };

  outputs = inputs@{ flake-parts, ... }:
    flake-parts.lib.mkFlake { inherit inputs; } {
      imports = [
        inputs.terranix.flakeModule
        ./modules/sites.nix
      ];
      systems = [ "x86_64-linux" ];

      sites.www = {
        subname = "www";
        domain  = "example.dedyn.io";
        ipv4    = "203.0.113.42";
      };
    };
}

It mentions a custom configuration option, sites.<name> defined in the following module:

nix modules/sites.nix
{ lib, config, ... }:
let
  cfg = config.sites;
in
{
  options.sites = lib.mkOption {
    default = { };
    type = lib.types.attrsOf (lib.types.submodule {
      options = {
        subname = lib.mkOption { type = lib.types.str; };
        domain  = lib.mkOption { type = lib.types.str; };
        ipv4    = lib.mkOption { type = lib.types.str; };
      };
    });
  };

  config = {
    # terranix configuration: one per site, deSEC DNS A record.
    perSystem = { ... }: {
      terranix.terranixConfigurations = lib.mapAttrs (name: site: {
        modules = [{
          terraform.required_providers.desec.source = "Valodim/desec";

          variable.desec_token.sensitive = true;
          provider.desec.api_token = "\${var.desec_token}";

          resource.desec_rrset.${name} = {
            domain  = site.domain;
            subname = site.subname;
            type    = "A";
            ttl     = 3600;
            records = [ site.ipv4 ];
          };
        }];
      }) cfg;
    };

    # NixOS module: importable from any nixosSystem, configures nginx.
    flake.nixosModules = lib.mapAttrs (name: site: { ... }: {
      services.nginx = {
        enable = true;
        virtualHosts."${site.subname}.${site.domain}" = {
          enableACME = true;
          forceSSL   = true;
          locations."/".return = ''200 "hello from ${site.subname}.${site.domain}\n"'';
        };
      };
      networking.firewall.allowedTCPPorts = [ 80 443 ];
    }) cfg;
  };
}

Use it #

Provision the DNS record (the deSEC token is read from the environment):

$ export TF_VAR_desec_token="…"
$ nix run .#www

Import the matching NixOS module into the host that should serve the site:

{ inputs, ... }: {
  imports = [ inputs.self.nixosModules.www ];
}

Adding a second site is two more lines in flake.nix:

sites.api = {
  subname = "api";
  domain  = "example.dedyn.io";
  ipv4    = "203.0.113.42";
};

Both nix run .#api and inputs.self.nixosModules.api appear automatically.

Their deployment is still handled as two separate steps.

info

The Valodim/desec provider is not on the public Terraform registry; consult the provider README for installing it (Nix users typically pin it via terraform-providers overrides or a local plugin cache).