← The specification

The image for a NAS

Rendered from spec at76d46f3 (2026-10-25) · its source · Edit this page

Status: Accepted · Audience: Operator · Area: L — Release & distribution


Purpose

Let somebody whose server is a NAS install lemonfiber the way they install everything else on it: as a container from a template. Unraid, TrueNAS SCALE and Synology all start apps from a container definition, and many of their owners never open a shell.

lemonfiber runs the stack through Docker, so the container needs the host’s Docker socket. That is the same power a native install gets from the docker group, and the image and its templates say so plainly (ADR-0038).

Behaviour

One image per release, built from that release’s binary

Once the release workflow has built a release and left it as a draft, the core repository’s release-image.yml builds ghcr.io/lemonfiber/lemonfiber for linux/amd64 and linux/arm64 from that release’s own binaries, after checking each against its digest and its attestation. It signs and attests the image as it does every other artefact (L1), and puts the templates on the draft before it is published. The image holds the binary and the two things it runs to drive the stack, the Docker command line and its Compose plugin, and nothing else: no shell, no package manager, no second process.

A template per platform, attached to the release

Each release carries a template for Unraid (added by URL), a TrueNAS SCALE custom app, a Synology Container Manager project, and plain Compose for Portainer and anything else. Each names the image by its digest. They live in the core repository under packaging/, so a template and the image it names are versioned together.

The stack’s directory has one path

Compose resolves a bind mount on the host, not in the container that asked. So the stack’s directory is mounted at the same path inside the container as it has on the host, and so is the data location its services mount. lemonfiber asks Docker which host path stands behind each one and refuses to start a stack when either differs, naming the mount. A check it cannot make, because Docker cannot be reached or does not know the container, refuses too.

The web surface keeps C6’s binding

The container shares the host’s network namespace rather than having one of its own. lemonfiber reaches the stack’s services on the host’s loopback, where they publish their ports, and a container’s own loopback is not the host’s. So the web surface, bound to loopback as it is everywhere, is on the host’s loopback. Offering it to the LAN is the operator’s choice, and lemonfiber still refuses a binding beyond loopback until a password is set (C6). The terminal UI is reached with docker exec.

Updating means a new image

lemonfiber in the image never replaces its own binary. Where it would offer an update (E2), it says to pull the new image and recreate the container instead.

Edge cases

Situation Behaviour
The stack directory is mounted at a different path inside lemonfiber refuses to start the stack, naming the host path and the container path.
The socket is not mounted lemonfiber says it cannot reach Docker and that the template mounts the socket, and starts nothing.
The NAS’s Docker group has an unusual GID The templates take the GID as a setting, so the container runs as that group rather than as root.
Somebody publishes the web port on the LAN with no password set The surface stays loopback-bound inside, and lemonfiber says why (C6).
An update is offered inside the image The command to pull and recreate is shown; nothing is replaced in place.

Acceptance criteria

ID Requirement
L3-R1 Each release MUST publish an image of lemonfiber for linux/amd64 and linux/arm64, built by the release workflow from that release’s own binaries, signed and attested as the release’s other artefacts are.
L3-R2 The image MUST contain the lemonfiber binary and what it needs to run, and no shell, package manager or second process.
L3-R3 Templates for Unraid, a TrueNAS SCALE custom app, a Synology Container Manager project and plain Compose MUST be kept in the core repository under packaging/, MUST name the image by digest, and MUST be attached to each release.
L3-R4 lemonfiber running in the image MUST refuse to start a stack whose directory is not mounted at the same path inside the container as on the host, naming both paths.
L3-R5 The templates MUST mount the Docker socket, MUST run the container as the host’s Docker group where the platform allows rather than as root, and they and the image’s documentation MUST state that the socket gives the container control of the host’s Docker.
L3-R6 The templates MUST publish the web surface on the host’s loopback, and lemonfiber in the image MUST keep C6’s refusal of a binding beyond loopback without a password.
L3-R7 The terminal UI MUST be usable through docker exec.
L3-R8 lemonfiber in the image MUST NOT replace its own binary, and where it would offer an update MUST give the command to pull the new image and recreate the container.