Zone Configuration

Zones are the primary organisational unit in Nixda Stack. Each zone maps to a VLAN, owns a /24 subnet, and holds a named set of services. This guide covers defining zones, placing services inside them, and using blueprint catalog entries to minimise boilerplate.

For the conceptual background on why zones exist and how they relate to the network topology, see the Architecture page.


Defining a Zone

Add an attribute under nixda.zones with three required fields:

nixda.zones.private = {
  vlan   = 20;
  subnet = "10.100.20";
  services = { };
};
FieldTypeDescription
vlaninteger (1–4094)IEEE 802.1Q tag. Must be unique across all zones.
subnetstringFirst three octets of the zone’s /24 (e.g. "10.100.20").
servicesattribute setNamed service definitions placed in this zone.

Zone names (private above) are arbitrary Nix identifiers. They appear in DNS names ({service}.{zone}.lan) and systemd unit names, so lowercase alphanumeric strings separated by hyphens are recommended.

VLAN IDs must be globally unique. Nixda Stack does not enforce this at evaluation time; duplicate VLAN IDs will produce a broken network configuration silently.


Placing Services

Add a named attribute under services. At minimum, every service requires id and exactly one runtime block:

nixda.zones.private.services.myapp = {
  id    = 10;
  ports = [ 8080 ];
  runtime.oci = {
    image = "myapp:latest";
  };
};

Service ID

id is an integer from 1 to 254 (type enforced), though 254 is reserved for the host bridge, making the usable range 1–253. It determines the service’s static IP address:

IP = zone.subnet + "." + id

For the example above with subnet = "10.100.20" and id = 10, the service receives the IP 10.100.20.10.

Constraints:

  • IDs must be unique within a zone. A NixOS assertion enforces this at evaluation time; duplicate IDs produce a build failure with a message listing the conflicting values.
  • ID 254 is reserved for the host bridge address (the host’s entry point into the zone).
  • The valid consumer range is therefore 1–253.

Runtime blocks

Exactly one runtime.* block should be non-null. The four available runtimes are:

BlockExecution model
runtime.ociPodman container from an OCI image
runtime.nativeNixOS container (systemd-nspawn)
runtime.systemHost-level systemd service (no isolation)
runtime.vmMicroVM with an isolated kernel

For detailed runtime configuration, see the Native runtime and OCI runtime pages. For lifecycle behavior (idle timeout, socket activation), see Service Lifecycle.

Additional service options

OptionTypeDefaultDescription
portslistOf port[]TCP ports the service listens on internally
idleTimeoutnullOr int43200Inactivity timeout in seconds; null for always-on
stripPrefixbooltrueWhether the reverse proxy strips the service-name path prefix
prefixMappingattrsOf port{}Maps URL path prefixes to specific internal ports

prefixMapping is defined in the service schema but is not yet wired into proxy-service.nix. It is reserved for future use — when implemented, it will allow a single service to route different URL prefixes to different backend ports.

runtime.system runs directly on the host with no network isolation. The service is reached via 127.0.0.1. Its configuration is a deferredModule composed manually by the consumer — see the cockpit and terminal blueprints for examples. A dedicated runtime page is not warranted; the implementation is minimal (firewall port opening only).


Using Blueprints

The catalog (config.nixda.catalog.{name}) exposes pre-configured service attribute sets that match the services submodule type exactly. Merge a blueprint into a service definition with lib.recursiveUpdate:

nixda.zones.private.services.uptime = lib.recursiveUpdate
  config.nixda.catalog.uptime
  {
    id = 15;
  };

lib.recursiveUpdate performs a deep merge. The right-hand side wins on conflicts. The blueprint provides the runtime defaults (configuration, ports, idle timeout); the consumer provides:

  • id — placement within the zone subnet.
  • Host-specific overrides — any attribute the blueprint explicitly leaves to the consumer.

lib.recursiveUpdate replaces lists rather than concatenating them. When a blueprint declares volumes and the consumer also declares volumes, the consumer’s list replaces the blueprint’s list entirely. Supply the complete desired list in the consumer stanza.

See the Blueprint Catalog page for the full list of available blueprints and the attributes each one requires the consumer to supply.


Service ID Allocation Reference

RangeAssignment
1253Available for service placement
254Reserved — host bridge address

IP formula: {zone.subnet}.{service.id}

Examples for subnet = "10.100.20":

idIP
210.100.20.2
1010.100.20.10
10010.100.20.100

Multiple Zones Example

A complete two-zone configuration using catalog blueprints:

{ config, lib, ... }:

{
  nixda.zones = {
    management = {
      vlan   = 10;
      subnet = "10.100.10";
      services = {
        uptime   = lib.recursiveUpdate config.nixda.catalog.uptime   { id = 2; };
        cockpit  = lib.recursiveUpdate config.nixda.catalog.cockpit  { id = 3; };
      };
    };

    private = {
      vlan   = 20;
      subnet = "10.100.20";
      services = {
        syncthing = lib.recursiveUpdate config.nixda.catalog.syncthing {
          id = 5;
          runtime.native.volumes = [
            "/data/syncthing:/var/lib/syncthing"
          ];
        };
      };
    };
  };
}

The resulting IP assignments:

ZoneServiceIP
managementuptime10.100.10.2
managementcockpit10.100.10.3
privatesyncthing10.100.20.5