Skip to content

Template: Terranix Demo

The terranix demo shows how to generate Terraform/OpenTofu configuration directly from den host definitions. Each host aspect contributes NixOS config and Terraform resources in a single declaration. The pipeline collects per-host terranix class modules via policy.instantiate, and terranix’s flake-module generates all outputs.

den.aspects.* ──┬── nixos → nixosConfigurations.<host>
└── terranix → packages.<host> (tofu apply/plan/destroy)
devShells.<host> (interactive shell)

Two pieces work together:

Pipelinepolicy.instantiate collects terranix class modules from each host’s scope subtree and stores the raw module lists at flake.terranixModules.<host>:

den.policies.host-to-terranix = { host, ... }: [
(den.lib.policy.instantiate {
name = "${host.name}-tf";
class = "terranix";
instantiate = { modules, ... }: modules;
intoAttr = [ "terranixModules" host.name ];
})
];
den.schema.host.includes = [ den.policies.host-to-terranix ];

Terranix flake-module — a perSystem bridge reads the pipeline-collected modules and feeds them into terranix’s terranixConfigurations option, which generates packages, apps (via passthru), and devShells:

perSystem = { pkgs, system, ... }: {
terranix.terranixConfigurations = lib.mapAttrs (_: modules: {
inherit modules;
terraformWrapper.package = pkgs.opentofu;
}) (config.flake.terranixModules or {});
};

Per-host isolation is automatic — applyInstantiates collects only from each host’s scope subtree, so web-1’s config contains only web-1’s resources.

templates/terranix-demo/
flake.nix # inputs: den, terranix, nixpkgs, etc.
modules/
den.nix # two hosts (web-1, web-2) with infra fields
flake-parts.nix # standard wiring
host-schema.nix # extends host schema: server-type, region, image
terranix.nix # policy.instantiate + terranix flake-module
aspects/
provider.nix # hcloud provider + token variable
server.nix # per-host hcloud_server resource
hosts/
web-1.nix # web-1 NixOS config (nginx)
web-2.nix # web-2 NixOS config (nginx)

Hosts carry infrastructure fields alongside standard den config:

modules/den.nix
den.hosts.x86_64-linux = {
web-1 = {
server-type = "cx22";
region = "fsn1";
users.deploy = { };
};
web-2 = {
server-type = "cx22";
region = "nbg1";
users.deploy = { };
};
};

The server-type, region, and image fields are added via a host schema extension:

modules/host-schema.nix
den.schema.host.imports = [ infraFields ];

A provider aspect (included in den.default) declares the Hetzner Cloud provider for all hosts:

modules/aspects/provider.nix
den.aspects.hcloud-provider = {
terranix = {
terraform.required_providers.hcloud = { source = "hetznercloud/hcloud"; };
provider.hcloud.token = "\${var.hcloud_token}";
};
};

A parametric server aspect generates a Terraform resource per host. It uses a host-qualified name so each host produces a uniquely-keyed module:

modules/aspects/server.nix
serverAspect = { host, ... }: {
name = "hcloud-server/${host.name}";
terranix = {
resource.hcloud_server.${host.name} = {
name = host.hostName;
server_type = host.server-type;
location = host.region;
image = host.image;
};
};
};

Terranix’s flake-module generates all outputs automatically:

OutputDescription
packages.<system>.<host>Default app (tofu apply) with passthru
nix run .#<host>Run tofu apply
nix run .#<host>.planRun tofu plan
nix run .#<host>.destroyRun tofu destroy
nix run .#<host>.initRun tofu init
devShells.<system>.<host>Shell with tofu + all scripts
nixosConfigurations.<host>NixOS system configuration
Terminal window
# Plan infrastructure for web-1
nix run .#web-1.plan
# Apply
nix run .#web-1
# Destroy
nix run .#web-1.destroy
# Interactive shell with tofu + scripts
nix develop .#web-1
# Inspect the generated config.tf.json
nix build .#web-1.config && cat result
# Verify NixOS configs still work
nix eval --override-input den . \
./templates/terranix-demo#nixosConfigurations.web-1.config.services.nginx.enable

The apps handle workspace setup and tofu init automatically:

Terminal window
TF_VAR_hcloud_token="your-token" nix run .#web-1.plan
TF_VAR_hcloud_token="your-token" nix run .#web-1

Or use the devShell for interactive work:

Terminal window
nix develop .#web-1
export TF_VAR_hcloud_token="your-token"
plan
apply
destroy
FeatureShown
Custom class (terranix)
policy.instantiate for module collection
Terranix flake-module integration
Per-host apps (plan/apply/destroy/init)
Per-host devShells
Per-host subtree isolation
Host schema extensions
Parametric aspects with host-qualified names
Dual-class aspects (nixos + terranix)
Contribute Community Sponsor