lemonfiber-media-stack — the stack itself
lemonfiber-media-stack
The Docker Compose stack Lemonfiber orchestrates: indexers, download clients, the *arr automation apps, Jellyfin and Seerr — 19 services, all open-source, all pinned.
Status: complete, not yet run on hardware. All 19 services are defined and every rule below is enforced in CI. The remaining M1 exit criteria are the two things CI cannot check — a hardlink import verified end to end, and the VPN killswitch verified by hand. See the spec and roadmap.
Runs without lemonfiber
Section titled “Runs without lemonfiber”This is the load-bearing property: it’s a standalone Compose project. Clone
it, set .env, and run it with plain Docker — no lemonfiber binary anywhere:
cp .env.example .env # set DATA_ROOT, and VPN creds if using torrentsdocker compose --profile search --profile usenet --profile torrent \ --profile tv --profile subs up -dThat five-profile set is the tv form. Forms are named profile sets, and
just up tv expands one for you straight from stack.toml:
just forms-list # search, dl, hunt, tv, movies, music, books, …just up tvThat’s what makes adopting Lemonfiber a reversible decision.
Requires Docker Compose v2.20 or newer — compose.yml uses include:.
The one rule
Section titled “The one rule”Every service gets exactly one ${DATA_ROOT}:/data mount. Downloads and
media live as subdirectories under it, on one filesystem, so imports hardlink
instead of copy. Splitting them into separate mounts silently breaks hardlinks —
CI rejects it. See spec
ADR-0006.
| File | What |
|---|---|
stack.toml |
The manifest Lemonfiber consumes — services, profiles, forms |
compose.yml |
Stitches the fragments together; no services of its own |
compose/ |
One fragment per profile — tv.yml, media.yml, torrent.yml, … |
compose/_common.yml |
Shared service defaults, reached via extends: |
.env.example |
Every variable, documented |
stacks/ |
Overlay: NAS/copy mode |
config/ |
Seeded templates for Recyclarr, Homepage and Caddy |
scripts/ |
The checks CI runs, all runnable locally via just |
Adding a service
Section titled “Adding a service”Three data edits, no code: a service block in the right compose/<profile>.yml,
a [[service]] in stack.toml, and its profile added to the relevant forms. Then
just ci. See spec
30-repos/lemonfiber-media-stack.md.
What CI enforces
Section titled “What CI enforces”Not style preferences — each has a spec requirement behind it, and each is
proven to fail when broken by scripts/test_validate_manifest.py.
| Check | Enforces |
|---|---|
| Manifest ↔ compose parity | Every service in one is in the other (REPO-R18) |
One ${DATA_ROOT}:/data mount per service |
Hardlinks (ADR-0006, C5-R5) |
| Bindings match the manifest tier | Admin on loopback, household on LAN (C6-R1/R2) |
No depends_on across a profile |
Any subset boots (B1-R14) |
| Killswitch routing | Nothing shares Gluetun’s profile without its namespace, so no client here is one lemonfiber must report as leaking (C2-R12) |
| Pinned, non-floating tags | Nothing changes because time passed (E1-R1) |
| Capabilities match the manifest | Only Gluetun holds NET_ADMIN (C6) |
| OSI licence per service | Verified against a vendored SPDX list (F2-R5) |
| Every form resolves | docker compose config per form (REPO-R17), dragging in nothing outside its profiles (B1-R14, REPO-R19) |
| arm64 + amd64 per pin | Read from each registry’s manifest list (F2-R6) |
The parity checks read docker compose config’s resolved model rather than the
YAML, so they check what Docker will run, not what the file appears to say.
Contributing
Section titled “Contributing”The spec is canonical — every change cites a spec identifier. Read AGENTS.md and the contributing guide.
just ci runs the checks above and turns on this repository’s pre-push hook,
which refuses a push that would leave a branch carrying no commit origin/main
does not — what pushing the trunk over a feature branch looks like. It is git config core.hooksPath .githooks, per clone, and just hooks does only that. A
clone where neither has run has no hook: git cannot read .githooks/ on its own.
Licence
Section titled “Licence”Hippocratic License 3.0. The bundled services are each independently open-source (GPL/MIT/Apache); this repo distributes configuration, not their code.
This page lives in another repository Rendered from lemonfiber/lemonfiber-media-stack at 4b6b82c, 2026-09-05. Read the source of this page