ADR-0026: TrueNAS SCALE distribution via Custom App compose
Metadata
- Status: Accepted
- Date: 2026-08-09
- Deciders: Eagraí Clainne Team
- Related: ADR-0006 (first-time setup flow — the bootstrap token the install guide leans on), ADR-0021 (SQLite default — what makes a single-volume app possible)
Context
The household NAS runs TrueNAS SCALE, and Eagraí Clainne should be installable
there as an app. Since SCALE 24.10 ("Electric Eel") the app system runs on
Docker Compose, and iX removed support for third-party catalogs: the
middleware holds exactly one catalog row, its git source is hard-coded to
github.com/truenas/apps, and catalog.update accepts only the
preferred_trains field. There is no supported way to register another
catalog.
Two routes exist to a real catalog entry (Discover page, install form, version-based upgrade alerts):
- Fork
truenas/appswith an extra train and swap the git remote of the catalog clone on the NAS (/mnt/.ix-apps/truenas_catalog). The swap survives normal syncs because the middleware pulls whateveroriginpoints at — but any failed pull makes it silently re-clone from the official GitHub URL, reverting the NAS to the stock catalog. Keeping the fork alive means mirroring upstream forever, never force-pushing, and running a watchdog cron on the NAS. - Custom App ("Install via YAML"): the supported path. The user pastes a compose file; TrueNAS manages the app lifecycle. There is no install form and no catalog version metadata, but TrueNAS watches the image digest behind the pinned tag and raises "update available" when it moves.
The image is already fit for this: distroless static, non-root (UID 65532),
read-only rootfs, one writable directory (/data), configured entirely by
EAG_* environment variables, published multi-arch to the family Forgejo
registry on every release.
Decision
Eagraí Clainne ships to TrueNAS as a Custom App compose file, not a catalog train.
deploy/truenas/custom-app.yamlis the canonical, paste-ready definition. It pinseagraiclainne:latestso TrueNAS digest-watching turns each release into an update alert, and it mirrors the k8s security posture (non-root, read-only rootfs, all capabilities dropped).- Each release attaches a tag-pinned copy
(
eagraiclainne-truenas_<version>.yaml) beside the CLI tarballs, covered by the same signed checksums file, for installs that must not move. - The server binary carries a
healthchecksubcommand (TCP dial of its own port) because a distroless image has no shell or curl for compose healthchecks. Exec form only:["CMD", "/server", "healthcheck"]. - The install guide lives in the docsite (
install-truenas.md), served at/docsfrom the binary.
Consequences
- Installation and every config change are YAML edits in the TrueNAS UI — no form, no validation beyond compose. Acceptable for a single-household audience that already runs the NAS.
- Updates work without any catalog infrastructure:
latestinstalls get digest alerts, pinned installs swap the release asset by hand. - No fork of
truenas/appsto mirror, no unsupported state on the NAS to watch. If iX ever restores third-party catalogs, a train can be revisited on top of the same compose content. - The compose file duplicates knowledge the timoni module also holds
(image, port,
/data, env names). Both are thin projections of the same server contract; a capability change that touches deploy shape updates both (system rule 1 applies to deploy surfaces in spirit).
Alternatives considered
- Forked catalog train (rejected): full catalog UX, but it rests on an origin-swap the middleware never promises to honour, with a silent-revert failure mode and a permanent upstream-mirroring duty. The cost lands on the NAS operator forever; the benefit is one nicer install that happens once.
- Replace the catalog with a minimal standalone one (rejected): the NAS runs other official-catalog apps (Gitea among them); replacing the catalog wholesale would orphan their updates.
- Plain
docker run/ Portainer-style sidecar (rejected): bypasses the TrueNAS app lifecycle (snapshots, upgrade alerts, UI state) for no gain over a Custom App.