Blueprint Catalog
Overview
The blueprint catalog (nixda.catalog) is a set of pre-defined service blueprints. A blueprint is a
complete, opinionated runtime configuration for a specific service — it encodes the image or NixOS
module, default ports, environment variables, and idle-timeout behavior. Blueprints do not contain
host-specific data such as IP assignments or zone placement; those are always supplied by the
consumer.
For runtime-specific details, see the Native runtime and OCI runtime pages. For zone placement and ID allocation, see Zone Configuration.
Blueprints are exposed as values in config.nixda.catalog.<name>. Each value is a plain attribute
set matching the service submodule type defined in nixda.zones.<zone>.services. This structural
equivalence means a blueprint can be merged directly into a zone service definition using
lib.recursiveUpdate.
What a blueprint provides
- Runtime — OCI image reference with default environment variables and volumes, or a NixOS
deferredModulefor native container / system service runtimes. - Ports — The TCP ports the service listens on internally.
idleTimeout— Default seconds of inactivity before the service is stopped.nulldisables idle management entirely (always-on).stripPrefix— Whether the reverse proxy strips the service-name path prefix before forwarding. Defaults totrue.
What the consumer always provides
id— The unique integer (1–254) used to derive the static IP for the service within the zone:10.100.<zone_id>.<id>.- Zone placement — Which
nixda.zones.<zone>.servicesattribute set the service is registered under.
Usage Pattern
The canonical consumer pattern is a lib.recursiveUpdate merge:
nixda.zones.private.services.jdownloader = lib.recursiveUpdate
config.nixda.catalog.jdownloader
{ id = 20; };
lib.recursiveUpdate performs a deep merge, with the right-hand side winning on conflicts. The
blueprint supplies everything the runtime needs; the consumer supplies identity and placement, and
optionally overrides any blueprint default.
This pattern keeps zone definitions minimal. A service that needs no customisation beyond id
and zone placement requires a single two-line stanza. A service that needs volume mounts or
environment overrides adds only those attributes.
The consumer is responsible for any mounts, inputs, or configuration that the blueprint explicitly documents as required. Blueprints cannot enforce these constraints at evaluation time; missing required values produce runtime errors rather than evaluation failures.
Blueprint Reference
The catalog ships the following blueprints. The table below summarises their runtime
characteristics. Blueprints marked with a digest-pinned recommended map (ADR-0005, all
OCI-runtime entries below) expose the
mk constructor surface for
explicit version pinning.
| Blueprint | Runtime | Port(s) | Idle timeout | Always-on |
|---|---|---|---|---|
terminal | System | 7681 | — | Yes |
uptime | Native (NixOS container) | 3001 | 12 h | No |
cockpit | System | 9090 | — | Yes |
copyparty | Native (NixOS container) | 3923 | 12 h | No |
jdownloader | OCI (Podman) | 5800 | 12 h | No |
syncthing | Native (NixOS container) | 8384, 22000, 21027 | — | Yes |
forgejo-runner | Native (NixOS container) | — | — | Yes |
tubearchivist | OCI (Podman) | 8000 | 12 h | No |
tubearchivist-es | OCI (Podman) | 9200 (internal) | 12 h | No |
tubearchivist-redis | OCI (Podman) | 6379 (internal) | 12 h | No |
twenty | OCI (Podman pod) | 3000 | disabled | Yes |
Consumer Override Patterns
Adding volumes to an OCI blueprint
OCI volumes are a list. lib.recursiveUpdate replaces lists rather than concatenating them, so
the consumer must supply the full volume list when overriding:
nixda.zones.private.services.jdownloader = lib.recursiveUpdate
config.nixda.catalog.jdownloader
{
id = 20;
runtime.oci.volumes = [
"/mnt/storage/downloads:/output"
"/var/lib/jdownloader/config:/config"
];
};
Overriding environment variables
OCI environment variables are an attribute set. lib.recursiveUpdate merges attribute sets
deeply, so individual variables can be overridden without restating the full set:
nixda.zones.private.services.jdownloader = lib.recursiveUpdate
config.nixda.catalog.jdownloader
{
id = 20;
runtime.oci = {
environment.TZ = "America/New_York";
volumes = [
"/mnt/storage/downloads:/output"
"/var/lib/jdownloader/config:/config"
];
};
};
Disabling idle timeout
To keep an idle-timeout-enabled service always-on, override idleTimeout to null:
nixda.zones.private.services.uptime = lib.recursiveUpdate
config.nixda.catalog.uptime
{
id = 10;
idleTimeout = null;
};
Overriding idle timeout duration
To use a shorter timeout than the 12-hour default (for example, 2 hours):
nixda.zones.private.services.copyparty = lib.recursiveUpdate
config.nixda.catalog.copyparty
{
id = 30;
idleTimeout = 7200;
runtime.native.volumes = [
"/mnt/storage/media:/media"
];
};
Pod Blueprints
Some blueprints use runtime.oci.pod to group multiple containers into a Podman pod
with a shared network namespace. Pod members communicate via localhost and are
invisible to other zone services — only the entrypoint gets a zone IP.
Example: Twenty CRM
Migrated blueprints expose the { recommended, mk } constructor surface
(see Pinning a Blueprint to an Explicit Version).
The consumer calls (blueprint "twenty").mk {} to inherit the recommended digest-pinned
versions, then merges host-specific identity and volumes via lib.recursiveUpdate:
crm = lib.recursiveUpdate ((blueprint "twenty").mk {}) {
id = 1;
runtime.oci = {
environment = {
SERVER_URL = "https://crm.business.lan";
};
volumes = [
"twenty-local-storage:/app/packages/twenty-server/.local-storage"
];
pod."twenty-db".volumes = [
"twenty-db-data:/var/lib/postgresql/data"
];
pod."twenty-worker".volumes = [
"twenty-local-storage:/app/packages/twenty-server/.local-storage"
];
};
};
Calling mk {} (no versions argument) inherits the recommended pin and emits one drift
warning per evaluation acknowledging the inheritance. To opt into the recommended pin
explicitly (suppressing the warning), supply versions = (blueprint "twenty").recommended;:
crm = lib.recursiveUpdate ((blueprint "twenty").mk {
versions = (blueprint "twenty").recommended;
}) {
id = 1;
# ... runtime.oci as above
};
For partial pinning (different main tag, recommended postgres/redis/worker) see the
per-blueprint twenty page. The partial-pin rule
(ADR-0004) rejects strict-subset keysets — supply all four keys or none.
Pod member volumes and environment can be overridden individually via
pod."member-name".volumes and pod."member-name".environment.
Secrets are injected per pod member via environmentFiles in the host’s
default.nix — see the consumer host documentation for the wiring pattern.