76d019f7e980.
Overview
Desired state is how Reefy converges an adopted device on its requested configuration. Reefy sends an authenticated configuration request, and the device applies it only when it differs from the state already accepted.
The device is split into two processes:
reefy-controlowns MQTT, command dispatch, status, and stage reporting.reefy-reconcilerowns storage, files, backups, and Docker Compose. Control calls it over the localio.reefy.ReconcilerVarlink interface.
This split keeps the MQTT control path available if storage or container work is slow, out of memory, or temporarily wedged.
Data flow
Requested device configuration
|
v
Authenticated control channel ---------> reefy-control
|
| local Varlink call
v
reefy-reconciler
persist request
reconcile host + apps
|
Device status and convergence <---------------'
Configuration contents
The requested configuration can include:
- Device identity, hostname, tunnel credentials, hardware, and user.
- Installed app instances, their pinned images, ports, slots, and overrides.
- User SSH keys, Wi-Fi, static network addresses, and storage selection.
- Optional provider integrations and app capabilities.
- App volumes, seed files, restore requests, and backup configuration.
Reefy treats the request as one coherent configuration. If it cannot safely represent every installed app, it leaves the device's accepted configuration unchanged rather than sending a partial request that could remove a workload.
App ports and routes
Each web app receives an available device-facing port. For a normal bridge-network app, Reefy forwards authenticated traffic to the app's declared container port without exposing that listener directly:
LAN URL port assigned by Reefy
container listener declared by the app, for example 8080
LAN and remote access pass through Reefy's authenticated routing layer. The app port is never bound directly to all host interfaces.
Host-network apps are an exception. Reefy routes to the app's declared port,
or injects APP_PORT_<default_port> when the manifest declares
dynamic_port: true.
Interactive terminals use Reefy's authenticated terminal bridge and do not open a separate public listener per app.
When state is pushed
Reefy publishes a configuration update when:
- a device is adopted;
- an adopted device comes online with pending changes;
- a user changes apps, versions, environment, Wi-Fi, network, storage, SSH keys, backup settings, or attached providers; or
- the user requests a manual resync.
An unchanged request is not applied again. A genuine configuration change is sent immediately.
Device-side apply order
For a new command, the data plane reads the old persisted state before saving the new document. That old/new diff is required for safe cleanup of removed static IPs and deleted per-app logical volumes.
The current apply order is:
- Persist
/mnt/reefy-data/state/desired-state.json. - Set the requested hostname, or restore the MAC-derived default.
- Apply Wi-Fi, using the old state to remove obsolete configuration.
- Provision or activate encrypted storage when requested.
- Apply static network addresses with old/new diff cleanup.
- Rewrite user SSH keys and synchronize per-app system users.
- Create and mount app volumes, including capped or backup-backed thin LVs.
- Write backup configuration and restore requested archives before apps start.
- Apply allow-listed rendered files under
/mnt/reefy-data/apps/or/mnt/reefy-data/state/. - Write Docker Compose and run
docker compose up -d --pull missing --remove-orphans. - Reclaim per-volume LVs belonging to instances removed from the new state.
Before Compose runs, the reconciler removes optional device mappings whose
/dev nodes are absent. It also verifies that every registered instance has a
matching Compose service. An inconsistent state is rejected rather than
allowed to remove an app as an orphan.
Concurrency and boot reconciliation
Both control and data-plane paths serialize applies. If a newer state arrives while an apply is running, the newest pending payload is queued and drained before the lock is released.
At boot, reefy-reconciler starts applying its saved state in a background
thread while it brings up the Varlink socket. On MQTT connection,
reefy-control calls Reconcile rather than reading or overwriting the state
file itself. This preserves the data plane's ownership of old/new diffs and
closes the startup race between offline reconciliation and the first server
push.
Convergence
After a successful apply, the device reports a deterministic fingerprint of the configuration it accepted. A matching fingerprint means no newer request needs to be applied. Per-app health remains the authority for whether every workload is actually running.
Control reports applying before the local data-plane call, reports
convergence after success, waits for proxy health, and then reports ready.
An apply failure reports error and does not claim convergence.
Compose failure policy
Compose is attempted at most five times. Failures are classified:
| Class | Behavior |
|---|---|
| Image missing or access denied | Fail immediately because retrying cannot fix the reference. |
| No space | Prune images once without volumes; retry only if space was reclaimed. |
| Docker layer or overlay corruption | Prune once with anonymous volumes, then retry. |
| Network, registry, timeout, or other transient error | Exponential backoff of 10, 20, 40, 80 seconds between attempts. |
After terminal failure, the reconciler stores a signature of the failed Compose document. An unchanged state is not repeatedly pulled on every reconnect. A changed state or a successful apply clears the guard.
Device implementation files
| File | Responsibility |
|---|---|
board/reefy/reefy/rootfs-overlay/usr/lib/reefy/reefy/control.py |
Authenticated command handling, stage reporting, and local data-plane calls. |
board/reefy/reefy/rootfs-overlay/usr/lib/reefy/reefy/dataplane.py |
Persist and apply configuration, Compose, backups, files, and cleanup. |
board/reefy/reefy/rootfs-overlay/usr/lib/reefy/reefy/storage.py |
Encrypted storage and per-volume lifecycle. |
board/reefy/reefy/rootfs-overlay/usr/share/varlink/io.reefy.Reconciler.varlink |
Control-to-data-plane method contract. |