Controlling a stack on another machine
Status: Accepted · Audience: Operator · Area: B — Running it
Purpose
Section titled “Purpose”Many operators don’t run this on the machine they sit at. The stack lives on a NAS, a mini-PC or a home server; the operator works from a laptop.
The requirement is modest — reach the stack from elsewhere on the home network — and the temptation is to over-build it into a client/server protocol with its own authentication and transport. That would be a large new attack surface for something a browser already solves.
Scope is LAN only. Access from outside the home network is deferred past 1.0 (see B7 note).
Behaviour
Section titled “Behaviour”Primary path: the web UI, reachable on the LAN
Section titled “Primary path: the web UI, reachable on the LAN”lemonfiber runs on the machine hosting the stack. Its web UI is normally bound to loopback; binding it to the LAN is an explicit opt-in.
on the server: $ lemonfiber ui --bind lan ✗ refusing: LAN binding requires authentication
$ lemonfiber auth set $ lemonfiber ui --bind lan ✓ http://nas.local:7171
from the laptop: open a browser.LAN binding cannot be enabled without authentication configured. This is a hard refusal, not a warning — the failure mode is an unauthenticated control surface for the whole media stack, reachable by every device on the network including ones the operator doesn’t administer.
Secondary path: a remote Docker context
Section titled “Secondary path: a remote Docker context”For operators who prefer the CLI, lemonfiber honours Docker’s own remote-host
mechanisms — DOCKER_HOST, or a named Docker context, typically over SSH. No new
transport, no new credentials: it reuses SSH keys the operator already has.
The remote-path trap, handled explicitly
Section titled “The remote-path trap, handled explicitly”With a remote Docker host, compose files and bind-mount paths resolve on the
remote machine, not the local one. lemonfiber up tv from a laptop looks for
stack files on the server.
Unhandled, this produces a baffling error naming a path that exists perfectly well on the machine the operator is sitting at. lemonfiber therefore detects a remote context and:
- Verifies the stack files exist on the remote host.
- Refuses with an explicit explanation if they don’t.
- States, in status output, which host is being operated on.
That last point matters generally: when a remote context is active, every command must say which machine it affects. Stopping the wrong stack because the shell had a context set is an easy and unpleasant mistake.
Whichever path, the stack runs in one place
Section titled “Whichever path, the stack runs in one place”lemonfiber does not orchestrate services across multiple machines. A stack lives on one host; this feature is about reaching it.
States
Section titled “States”| State | Meaning |
|---|---|
local |
Operating on the local Docker daemon; UI on loopback |
lan-served |
Web UI bound to the LAN with authentication configured |
remote-context |
Operating a remote Docker daemon via context or DOCKER_HOST |
remote-unverified |
Remote context active but stack files not confirmed present |
refused |
LAN binding requested without authentication |
Edge cases
Section titled “Edge cases”| Situation | Behaviour |
|---|---|
--bind lan without auth configured |
Refuse, and explain how to configure authentication. Never bind with a warning. |
| Remote context set, stack files absent remotely | Refuse before running anything, naming the host and the expected path. |
| Remote context set unnoticed | Every command’s output names the target host. |
| Server hostname unresolvable from the laptop | Report a name-resolution failure distinctly from a connection refusal. |
| SSH key not accepted | Report it as an SSH authentication failure with the host named — not as “Docker unavailable”. |
| Docker API version mismatch between hosts | Detect at connect and report both versions. |
| Two operators using the web UI at once | Serialise lifecycle operations; show that another operation is in progress. |
| Session over an unencrypted LAN connection | State plainly that traffic is unencrypted HTTP on the local network, and that credentials traverse it. Do not imply TLS where there is none. |
| Laptop sleeps mid-operation | The operation continues server-side; on reconnect the UI shows the outcome. |
| Native-mode Jellyfin on the remote host | Reported as host-managed; lemonfiber does not control the remote OS service manager. |
| Auth configured then removed while LAN-bound | Immediately revert to loopback and state why. |
Acceptance criteria
Section titled “Acceptance criteria”| ID | Requirement |
|---|---|
| B6-R1 | The web UI MUST bind to loopback by default. |
| B6-R2 | LAN binding MUST be an explicit opt-in and MUST be refused unless authentication is configured. |
| B6-R3 | Removing authentication while LAN-bound MUST immediately revert to loopback binding. |
| B6-R4 | lemonfiber MUST honour DOCKER_HOST and named Docker contexts. |
| B6-R5 | With a remote context active, lemonfiber MUST verify stack files exist on the remote host before executing any lifecycle operation. |
| B6-R6 | With a remote context active, every command’s output MUST name the host being operated on. |
| B6-R7 | Name-resolution failure, connection refusal and SSH authentication failure MUST be reported distinctly. |
| B6-R8 | Concurrent lifecycle operations from multiple clients MUST be serialised. |
| B6-R9 | When serving over unencrypted HTTP, lemonfiber MUST state that the connection is not encrypted. |
| B6-R10 | lemonfiber MUST NOT orchestrate services across more than one host. |
| B6-R11 | Docker API version mismatch MUST be detected at connection and MUST report both versions. |
| B6-R12 | Client disconnection MUST NOT abort a server-side operation already in progress. |
Related
Section titled “Related”- C6 Web UI security & binding policy — the authentication model
- B2 Lifecycle control — the operations being issued remotely
- G1 Interface tiers — the web UI itself
- ADR-0008 Hybrid Docker access
This page lives in another repository Rendered from lemonfiber/spec at 1d10402, 2026-09-09. Read the source of this page