Skip to content

Per-App channels (stable/beta/dev) — share /data across pre-release variants #172

@agners

Description

@agners

Problem statement

There is an obvious, ongoing need for Apps (formerly add-ons) to ship pre-release builds alongside their stable release so users can opt into testing without losing their existing setup. The problem is that Supervisor has no first-class concept of an "App channel" — so the ecosystem implements this with two distinct workarounds, both of which have significant downsides:

Workaround A: option flag inside a single App. One slug, one container image that bundles both runtimes; a config option (commonly beta: true) switches behavior at startup.

  • OpenThread Border Router (home-assistant/addons, openthread_border_router) — has a beta: false option in its schema.
  • Matter Server (home-assistant/addons, matter_server) — has beta: false. Its run script inspects the flag and, if true, npm installs matter-server@latest and execs the JavaScript Matter Server; otherwise it runs the Python Matter Server. The stable image therefore ships both the Python runtime and a full Node.js + npm toolchain so beta can be activated by toggling a checkbox. There are also escape-hatch options (matter_server_version, matter_sdk_wheels_version, matter_server_args, matter_server_env_vars) so power users can pin a specific upstream version — each of which is essentially a workaround for not being able to install a different image.

Tradeoffs: same /data and options survive switching, which is the property authors actually want — but the stable image carries a beta runtime, the schema is polluted with channel-control fields, and switching back/forth happens via config edits with no safety net.

Workaround B: separate Apps with distinct slugs. Technically cleaner image separation, allows parallel install in theory, but the App's persistent state is tied to the slug, so users either lose state when migrating or the project has to push state out of /data into shared folders.

  • ESPHome (esphome/home-assistant-addon) — three slugs: esphome, esphome-beta, esphome-dev. URLs differ (esphome.io / beta.esphome.io / next.esphome.io). All three map config:rw (Home Assistant's main config/ folder) and store device configs there rather than in /data — that's how migration works in practice. Default port 6052 is shared, so parallel install collides unless reassigned.
  • Zigbee2MQTT (zigbee2mqtt/hassio-zigbee2mqtt) — zigbee2mqtt (stable) and zigbee2mqtt-edge. Different images (ghcr.io/zigbee2mqtt/zigbee2mqtt-{arch} vs …-edge-{arch}), shared state via share:rw + homeassistant_config:rw. Identical port mappings, so parallel install collides.
  • Frigate (blakeblackshear/frigate-hass-addons) — six variant slugs: frigate, frigate_beta, frigate_fa, frigate_fa_beta, frigate_oldcpu, frigate_proxy. Same image with different tags; many overlapping ports.
  • Music Assistant (music-assistant/home-assistant-addon) — four slugs: music_assistant (stable 2.8.7), music_assistant_beta (2.9.0b13), music_assistant_dev (1.5.2 — a different upstream branch), music_assistant_nightly.

Tradeoffs: clean image and per-channel options schema, no runtime is conflated. But state doesn't carry over: communities steer users to keep config in HA's config/ or share/ (fragmenting backups and Supervisor's data model), and parallel install — the supposed benefit — usually doesn't work without manual port surgery because every variant ships the same defaults.

Why this matters for OHF. App quality depends on getting more users onto pre-release builds before stable cuts. Both workarounds make opting-in friction-heavy or risky:

  • Workaround A asks users to flip a hidden checkbox with no rollback, and pushes the stable image to permanently ship a beta runtime.
  • Workaround B asks users to reinstall and lose their setup, then re-onboard devices/credentials in the beta variant.

Neither encourages broad testing. The result is fewer testers, fewer pre-release bug reports, and bugs that land in stable.

Community signals

The strongest signal today is the breadth of the workarounds themselves — five of the most-installed Apps in the ecosystem (Matter Server, OTBR, ESPHome, Z2M, Frigate, Music Assistant) have all independently invented a channel mechanism, and none of them are quite the same.

From Supervisor repository:

Scope & Boundaries

In scope

  • Allow one App to declare multiple channel variants (stable / beta / dev) from a single repo directory, sharing slug, /data, options, ingress route, and discovery.
  • Supervisor-side channel switching with a safety net (automatic per-App backup before switch).
  • API and frontend surface to view and select an installed App's channel.
  • Full backward compatibility — existing single-config.yaml Apps behave exactly as today.

Not in scope

  • Cross-channel /data migration logic — remains the App author's responsibility, like any major-version upgrade.
  • Automatic channel upgrades — switching is always user-initiated.
  • Replacing the existing standalone pre-release Apps on day one. This is opt-in for App authors; existing duplicated slugs can keep working.
  • Parallel install of multiple channels of the same App — explicitly not supported (the whole point is one logical App).

Foreseen solution

App authors place additional manifests in the same directory of their store repo, with a channel suffix derived from the filename:

my-app/
├── config.yaml          ← stable
├── config.beta.yaml     ← beta
└── config.dev.yaml      ← dev (optional)

All variants share the same slug: and (typically) most of the manifest, but declare a different image: and version:. No new manifest field is introduced.

Supervisor groups these into a single logical App. The user-visible model:

  • The Store lists one App with an available_channels set.
  • An installed App carries a persisted channel (defaults to stable).
  • Switching channel is a single Supervisor operation: take a partial backup of just this App, swap the image (re-using the existing update path which already handles image-name changes), and start the new variant against the existing /data and options. On failure, roll back and surface a restore suggestion.
  • New endpoint: POST /addons/{slug}/channel. Info responses gain channel and available_channels.

Conceptually the same as Home Assistant Core's own update channel, but per-App.

Risks & open questions

  • Options-schema divergence across channels. If beta tightens or renames an option, stable's stored options can fail validation on switch. Mitigation: validate current options against the target schema before pulling the image; abort early with a suggestion to reset options if invalid.
  • /data layout differences. Not handled by Supervisor — author responsibility, same as a major version upgrade today. The pre-switch backup is the safety net.
  • App author migration cost. Frigate's frigate_fa (full-access) variant is a capability split, not a channel split — it shouldn't be folded into channels. We should document clearly what channels are and are not for so authors don't try to model unrelated forks (e.g. oldcpu) as channels.
  • Frontend UX. How prominent is the channel selector? Always visible per App, or hidden behind "advanced"? How do we signal beta risk without being alarmist?
  • Naming. Core already uses "channel" for the Supervisor/OS/Core update stream. Reusing the term for Apps is intuitive but could be confused; consider whether "App channel" is sufficiently distinct in UI copy.
  • Discovery/migration for existing duplicated Apps. What is the recommended path for projects with already-deployed foo + foo-beta slugs to consolidate without breaking existing installs? Likely needs a migration tool or documented one-time backup-restore-into-stable flow.
  • Pull-failure mid-switch. The old container is stopped before the new image pulls. On failure, the App stays stopped until the user restores. Needs clear UX.

Appetite

Small–medium.

  • Supervisor: contained — store scanner change, one new state field on installed Apps, one new operation that reuses the existing update path, one API endpoint.
  • Frontend: per-App selector + an info pill + a confirmation dialog highlighting the auto-backup.
  • Docs + author guidance: the longer tail — how to structure repos, schema-compat guidance, recommended migration story for existing duplicated Apps.

Roughly 1–2 cycles of focused work across Supervisor, frontend, and developer docs.

Decision log

Date Decision Outcome

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    Status

    Draft

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions