Out of Scope - ehharvey/homelab-ops GitHub Wiki
This page tracks work that is out-of-scope, but still worth documenting.
- Running more than one Incus cluster member — no longer "deferred with no home," as of 2026-09-20 this is Roadmap Phase 4 (
Decisions.md§26), and since 2026-09-28 the project's main line (Decisions.md§27). It is tracked as its own set of issues: a manual join spike, membership config, the joiner seed and joiner cert, the agent-driven join, an agent on every member, storage/network parity, real node-loss proof, and add/remove + quorum recovery. Cluster-group placement is paused. Phase 3 only takes Incus from a bare daemon to a real one-member cluster (Decisions.md§26 Tier A), which is what the App Manager's leader-election design (Decisions.md§ App Manager HA) actually needs. Wrapping Operations Center stays out, but how it joins members is worth reading before building the agent-driven join (Decisions.md§27). - GitOps auto-apply and rollback (today: diff-and-warn only)
- Private repos, repo-sharing across environments
- IPv6, DHCP/DNS write-back
- Cert rotation/revocation, moving off self-signed to a real CA
- Multi-disk / multi-NIC instance overrides
- Commit-hash-per-node tracking
- Phone-home / hardware-manifest reporting over Tailscale (a one-shot manifest ping from a node to the app — distinct from the continuous logs/metrics telemetry now in Phase 3 scope, see
Roadmap.md/Decisions.md§ Metrics — and distinct from the pre-install helper-OS registration/confirmation flow underDecisions.md§ Multi-OS support, which is interactive/blocking rather than a one-shot post-install ping) - Migrating the web app's own runtime from its Docker Compose/binary deployment into the IncusOS fleet
- Running the web container as a non-root user (defense-in-depth): switch the final Dockerfile stage to distroless's
nonrootuser (uid 65532) / the:nonrootimage tag. Tackle after Phase 3 — 0.x is single-user/no-auth (below), so root-in-container isn't a headline risk yet. Read-only config mounts (e.g.CLIENT_CERT_PATH) and/tmp(mode 1777) already work as non-root; the one thing to verify first is that a mountedSTORE_PATHvolume (the sqlite file) is writable by uid 65532, or the store fails to open on startup. - Standardized schema output for the fleet-definition format and/or the web app's REST API: JSON Schema generated from
internal/config's structs (editor autocomplete, CI validation of fleet YAML) and/or OpenAPI for the existing HTTP API (seeArchitecture.md§ HTTP API). No GraphQL — the app's scope is simple CRUD-ish config management, not query-heavy enough to justify a schema-first GraphQL layer. This is the expected growth path for validation if it ever outgrows the hand-rolledconfig.Validatechosen inDecisions.md§ Validation approach — generate the schema from the (typednet/netip) structs, rather than adopt a schema-DSL library. - Multi-user on web app. 0.x should focus on just 1 user for now. No auth needed.
- Supporting more than IncusOS as a managed-node target. Two directions are planned but not yet built (see
Decisions.md§ Multi-OS support): plain Debian w/ Incus, installed via a systemd-repart-based helper OS with a pre-install hardware-registration/confirmation flow; and Talos (Kubernetes nodes), single-node install first, full cluster lifecycle (control-plane/worker roles,talosctl bootstrap) deferred further still. Both are blocked on the OS-target abstraction and node→app networking work described there. - DB-specific (or other renderer-specific) write-promotion wiring for
kind: Appbeyond the genericRenderer.Promoteseam (see #92,docs/AppManager.md, andDecisions.md§ Stateful app support for the shape this would take) - Backup/restore for
kind: Appdata/volumes. Note this is a prerequisite for any real managed database, not a nice-to-have: replication is not backup — aDROP TABLEreplicates perfectly (Decisions.md§ Stateful app support). -
Multi-instance renderers — the cardinality half of this is in scope (
replicas: N/per-nodeonkind: App, and a reconcile loop keyed by(App, member); seedocs/AppManager.md), but the renderers that would need N > 1 are not: a database (class 3) or a quorum member (class 5) needs volumes, version-skew classification and DB-native HA beforereplicas: 3means anything. Seedocs/AppClasses.mdandDecisions.md§ Stateful app support. -
Cross-node App placement/scheduling/bin-packing.
kind: Apphas no placement field in 0.x — Incus's own scheduler decides, which is moot on today's single-member cluster. Placement is notreplicas: that says only how many, never where (App.Nodewas deleted precisely because it meant both). When real placement is eventually needed, the intended mechanism is Incus's own cluster groups (tag members with a capability, target creation at the group) via a separate field, invalid in combination withreplicas: per-node, not an operator naming individual nodes — seeDecisions.md§ App Manager HA anddocs/AppClasses.md§ Placement is a third axis. - Persistent volumes / storage schema for
kind: App. There's no volume field at all today, which is what confines every App to being stateless (or destructively updated —docs/AppClasses.mdclass 6). Needed before a database, and it brings its own lifecycle questions (does a volume outlive its generation? per-member or per-generation? orphan policy?). - Version-skew classification for
kind: Appupgrades. A bump is detected as "declaredimagediffers from the live instance's tag" — deliberately coarse, and sufficient for every renderer 0.x ships. It is not sufficient for classes 3/5: Postgres physical replication requires matching major versions (16 → 17 can't blue-green at all — that'spg_upgrade/logical replication), and Kubernetes has an N-2 skew policy plus control-plane-first ordering. Seedocs/AppClasses.md§ Version skew. - Run-to-completion / batch Apps (a migration or backup job).
Renderer.Healthyanswers "healthy right now"; a job wants "exited 0", and blue-green is meaningless for it. This is the natural home for a database backup sidecar if one is ever needed. - A stable endpoint/service address for
kind: App. Apps get whatever address Incus gives them; there's no field for a static IP or a service name, and DNS write-back is out of scope (above). This is unresolved rather than decided — a proxy fronting a database needs a stable address to be useful, which would make itdocs/AppClasses.mdclass 6 (scarce resource, needing a floating-IP handoff) rather than the class 1 it otherwise looks like. -
Cross-App reference / service discovery — how one App learns another App's members' addresses. There's no mechanism at all today: instances get whatever address Incus assigns, there's no service DNS, and no field lets an App name another App. This is the binding constraint on the proxy-in-front-of-a-database topology, ahead of anything else on this list — the proxy can only "health-check the DB layer directly" (
Decisions.md§ Stateful app support) if it can enumerate the DB's instances first. Prior art points at a reference field rather than a grouping construct: CloudNativePG'sPoolernames itsCluster(cluster: {name: …}); Patroni + HAProxy does the same via templated backend lists. Related but distinct from the endpoint entry above: that one is "how do clients reach an App", this one is "how does an App reach another App". -
Composition / multi-role deployments (grouping several Apps into one versioned unit — a
kind: Deployment, Helm-release, or operator-CR shape).replicasis homogeneous fan-out: N copies of one image driven by one renderer, differing only by ordinal. A proxy + writer + readers is several images, several roles, and up to three different classes, and nothing composes them — different roles are different Apps, related only by convergence. Deliberately not built, and the reason is the useful part: a grouping construct would be the union of three constraints that are each already deferred — dependency ordering (below), version-skew classification (above), and shared/orphaned teardown (below). Adding the container before any of those exist would group nothing. Apps are independent by construction, so this stays purely additive whenever a real constraint justifies it. Revisit trigger: the first workload with a genuine cross-App constraint — concretely Kubernetes, where the control plane must upgrade before workers under an N-2 skew policy. Note a database + proxy is not that trigger: the proxy is version-independent of the database and discovers it by health check, which is why the prior art keeps them as separate objects (see the cross-App reference entry above). Vocabulary warning, since this repo leans on k8s terms:replicasalready is a k8s Deployment; what this entry defers is closer to a Helm release. - App-level dependency ordering (no "App B waits on App A" graph). Note this is one of the three constraints the composition entry above is waiting on — and that startup ordering specifically doesn't need it: Patroni retries its DCS forever and a proxy retries its backends, so eventual convergence covers it without a graph.
- Automatic rollback after a successful App promotion (health-gating is strictly pre-promotion)
- Orphaned-App teardown (App removed from git; live instances not auto-deleted)
- Operator-facing UI/API for App/rollout status (beyond
incus list) - Self-hosted ACME CA (e.g. step-ca) so nodes fetch a real, chain-verifiable server certificate for their Incus API instead of the self-signed one Incus generates at first boot. Raised alongside #91:
internal/nodeprovision/cmd/validate-tunnel-harness's direct-to-Incus TLS clients currently setInsecureSkipVerify: true(dismissed as an accepted CodeQL finding, not fixed), since there's no CA to chain-verify against and nothing in the seed tells the web app what a node's server cert will be in advance — trust instead comes from the WireGuard tunnel's own peer authentication plus the client cert. A self-hosted ACME CA the web app trusts, with IncusOS configured (seed-time or post-boot) to fetch its server cert via ACME from that CA, would let these calls do real server-certificate verification instead of skipping it — closing that gap properly rather than just accepting it. Overlaps with the existing "moving off self-signed to a real CA" item above, but concrete enough (specific tool, specific trigger) to track on its own. Needs: confirming IncusOS actually exposes an ACME-configuration hook (seed file orincus-osdAPI, same kind of gap the Tailscale seed-hook investigation inDecisions.md§8 hit), and deciding where step-ca itself would run (web app process vs. a separate managed service). -
Automated leader election — 0.x ships operator-designated leadership (
docs/Decisions.md§25): failover is a person committing a newprimary. The new primary waits for the old one to stand down, fenced by git commits and an acting handoff. The automated ranked election over Incus heartbeats is specified in §25 (claim/verify protocol from single-writer registers, self-fencing, a takeover dead-time) precisely enough to build behind the existingleaderelection.Electorinterface; what to verify first is cross-agent read visibility and the clustered path. Also deferred: Temporal as the orchestrator (which would remove the leader altogether), and Kubernetes-hosted workloads.