Skip to content

Adding a service

Adding a service to the stack requires no lemonfiber change and no lemonfiber release. It is two data edits, and then the checks.

  1. A services/<id>.toml file holding its [[service]] entry, named in the include list of stack.toml.
  2. Its profile added to whichever forms should carry it.

Then just ci in the stack directory. If you are working on your own stack rather than the bundled one, point lemonfiber at it with --stack-dir.

This page is about changing the stack itself. To run one more service beside the bundled stack without keeping a stack of your own, install it as a plugin instead: lemonfiber writes its container and wiring from a manifest, and takes it out again as one operation.

Nobody writes one. lemonfiber generates the stack’s whole Compose project from the declarations of every service, the stack’s and every installed plugin’s: there is no fragment, no shared template and no overlay to edit. The image, the profile, the published port and its bind, the health check and the mounts all come from the service file below.

Every service that touches the library has exactly one data mount, at /data. Downloads and media live as subdirectories beneath it, on one filesystem, so an import hardlinks instead of copying. A service says it takes data with takes_data = true, and lemonfiber writes that one mount from the operator’s DATA_ROOT setting, the same for every service; there is no second mount to declare. What that looks like from the operator’s side is hardlinks and one mount point.

The [[service]] entry is where lemonfiber learns what the service is. Each one lives in a file of its own under services/, and the root’s include list decides which of them the stack holds, in that order. Every required field is listed in the stack manifest contract; the ones people forget are describes and without_it, which are what let a failure be judged rather than merely reported, and last_release, which is the latest release upstream has published rather than the one you pinned.

id is the name the generated Compose entry takes, and profile must be exactly one declared profile. image carries neither a tag nor a digest: tag holds an explicit tag, because a floating one fails validation and nothing in this stack should change because time passed, and digest holds the multi-architecture index the tag named, which is what Compose runs.

This is Sonarr’s file:

The release this page is built for does not carry what this part is read from. The next version's page has it.

just ci runs every check that needs no network: the manifest against the contract, every form resolving to services from its own profiles only, and the rules a change to the manifest must carry. CI adds the networked ones — each pin read from its registry, the upstream licence asked of its forge where a pin moves, and every profile that needs no VPN subscription started for real. Every check has a specification requirement behind it, and each is itself proven to fail when broken. The stack’s maintaining guide lists them all.

Your own stack is yours. For the bundled one, a service enters only if it is open source under an OSI-approved licence, publishes native linux/arm64 and linux/amd64 images, is actively maintained, does something no included service already does, and works without a paid tier. An image lemonfiber builds from its own code carries lemonfiber’s own licence, Hippocratic-3.0, instead.

Maintenance status is established from a project’s commit and release history, never from its own description of itself. just candidate <url> reads that history and answers admit, watch or reject. A fork advertising itself as an actively maintained successor, with no releases and no published image, is a dead fork — and a dead fork is worse than a slow-moving project that works.

Stating the criteria is what makes “why is X not included?” answerable, and makes an addition a judgement against a standard rather than a matter of taste.

What lemonfiber does with a service it does not know

Section titled “What lemonfiber does with a service it does not know”

It keeps working, generically. Lifecycle and status operations run on a service lemonfiber has never heard of, and it is shown with an unknown description rather than hidden. Features that need specific knowledge — wiring it to the other services, reading its queue health — report as unsupported for that service rather than failing the whole command.

The same applies in reverse. Remove a service lemonfiber depends on and it reports which features become unavailable; it does not refuse to run.

The stack manifest contract is the field-by-field reference, and the stack manifest is what to do when lemonfiber refuses one. Running without lemonfiber is the compose project on its own, and the media-stack repository is where these files live. The specification for this is F3 stack manifests.