Migration from an existing stack
Rendered from spec at76d46f3 (2026-10-25) · its source · Edit this page
Status: Accepted · Audience: Operator · Area: A — Getting started
Purpose
Many prospective operators already run some of this. They have a hand-rolled compose file, a Sonarr instance with 400 monitored series, indexers configured over three years, and — most importantly — a library they will not risk.
For them the question isn’t “how do I set this up,” it’s “can I adopt this without losing what I have?” If the answer requires starting over, they won’t adopt. And the loudest voices in the community are exactly these people.
Migration is also the lowest-risk way to earn trust: an operator who sees lemonfiber correctly identify their existing setup, without touching it, believes the rest of the tool.
Behaviour
Detect first, change nothing
Migration begins as a read-only survey. lemonfiber looks for evidence of an existing setup and reports what it found before proposing anything:
| Evidence | What it tells us |
|---|---|
| A running container matching a known service image | Which services exist, on which ports |
An existing config.xml or app database |
API keys, root folders, download client wiring |
| A compose file in the working directory | The current topology and volume layout |
| Media directories with recognisable structure | Where the library lives and how it’s organised |
The survey output is a plain statement of findings. Nothing is modified.
Where each service keeps its configuration and where its library is are findings
like any other, so the survey’s machine-readable answer carries both for every
service it found, or says it could not read them. No surface works either out
from a service’s name or its ports (A5-R14).
Adoption, not replacement
The default posture is adopt: keep the operator’s existing services, config, and library, and place lemonfiber in front of them as a control surface. Wholesale replacement is offered but never preselected.
| Mode | What happens |
|---|---|
| Adopt (default) | lemonfiber manages the existing containers and reads their configuration. Nothing is recreated. |
| Import | lemonfiber creates its own stack but copies indexers, root folders, and monitored items across via API. |
| Replace | lemonfiber stands up a clean stack; the operator’s old one is stopped but not deleted. |
| Side-by-side | lemonfiber runs on alternative ports alongside the existing setup so it can be evaluated without commitment. |
Side-by-side deserves emphasis: it lets a cautious operator evaluate with zero risk, which is exactly what this audience wants.
The alternative ports are recorded as one of lemonfiber’s own settings and read by the generator, which writes each service’s published port from them. Standing beside writes no Compose overlay: the only overlay is the operator’s own (ARCH-R252). Recording them is journalled, and undoing it puts the ports the bundle declares back.
Replacement is the mode that stops somebody’s running services, so its yes is the
offer its reading answered rather than a bare confirmation. The reading names what
it would stop; between reading it and agreeing, a container can be started or a
project can change. The replacement builds the offer again from what is running
when it acts and refuses by name where the two differ, so what stops is what the
operator was shown (A5-R13).
The library is never moved without explicit instruction
The library is the irreplaceable part — often years of accumulated content. lemonfiber may read it, index it, and point at it. It must never move, rename, or reorganise it as part of migration.
If the existing layout violates the single-mount rule, lemonfiber reports the consequence and offers a remedy — but the operator decides. Correctness does not outrank their data.
What cannot be migrated is stated plainly
Some things won’t transfer: bespoke custom formats, per-indexer tuning, scripts hooked into an *arr’s event system. lemonfiber enumerates what it could not carry across, rather than implying a complete migration and leaving the operator to discover gaps.
On the companion
The four modes above are stances, and N7-R2 requires the stance rendered as it
was given (N7): unchanged, pending and
blocked are not applied. Side-by-side is the one that outlives the operation
— a service placed on an alternative port to avoid a conflict stays there, and
N7-R6 requires it reachable from the app afterwards rather than shown once, at
the moment the port was chosen, and then lost.
What cannot be migrated is stated plainly is the part a small screen is most
likely to lose, so N7-R1 makes it unignorable: what an import could not carry
is shown with a reason for each, and is not subordinated to a success message. A
phone has room for one headline, and a list of gaps underneath Migration
complete is a list nobody reads. An import that carried nothing is a third
answer, told apart from one that has not run (N7-R9).
Where a move would take something over destructively, N7-R4 requires the copy
the stack wants first to be stated before the decision is agreed to rather than
reported after it, and N7-R5 requires a conflict to name what already holds the
thing it conflicts over — port in use, without saying by what, is not something
an operator away from the machine can act on. A refusal carries the stack’s own
reason (N7-R3); rendered as a generic failure with a retry button, it becomes
an invitation to do the same thing again.
Replacement stops the operator’s existing stack without deleting it, and
N7-R18 requires the app to show what would stop, what stopped and what is still
running — and forbids presenting a replacement as done while anything it replaces
is still up, whatever its stance says.
The survey changes nothing, which is what a rehearsal is, and N7-R10 holds a
rehearsed run here to the same labelling N6-R1 requires everywhere else.
States
| State | Meaning |
|---|---|
none-detected |
No existing setup found; normal setup proceeds |
surveyed |
Existing setup found and described; nothing changed |
plan-proposed |
A migration mode selected and its actions listed |
migrating |
Executing the plan |
adopted |
lemonfiber is managing pre-existing services |
imported |
Configuration copied into a lemonfiber-managed stack |
partial |
Some items migrated, some could not; unmigrated items enumerated |
Edge cases
| Situation | Behaviour |
|---|---|
| Existing services on ports lemonfiber wants | Detect before proposing anything. Offer adopt, or remap lemonfiber’s ports for side-by-side. |
Existing setup uses separate /downloads and /media mounts |
Report that hardlinks are not working today and quantify the cost. Offer a remedy; do not require it. |
| Existing *arr version newer than the pinned one | Refuse to downgrade. The database has already migrated and older binaries cannot open it. Offer to adopt at the existing version instead. |
| Existing *arr version much older | Warn that adoption will migrate the database irreversibly, and require a backup first. |
| Config files present but services not running | Read config, report findings, offer import without needing the services up. |
| Two conflicting existing setups | Report both and require the operator to choose. Don’t guess. |
| Library layout unrecognisable | Report honestly that structure could not be determined; offer manual root-folder specification. |
| Existing setup uses a different download client (NZBGet, Transmission, Deluge) | Adopt it where it is supported; otherwise state clearly that it is unsupported and what the alternatives are. |
| Existing Plex installation | Detect and report it. Explain that Jellyfin is a separate server and the two can coexist; offer library-path reuse. Do not disparage the choice. |
| Docker volumes rather than bind mounts | Report that the library is inside Docker-managed storage, which limits host visibility, and describe what adoption can and cannot do. |
| Migration fails partway | Leave the original setup functional. This is the non-negotiable property: a failed migration must never break what already worked. |
Acceptance criteria
| ID | Requirement |
|---|---|
| A5-R1 | Migration MUST begin with a read-only survey that modifies nothing. |
| A5-R2 | The survey MUST report every detected service, config source, and library location before any plan is proposed. |
| A5-R3 | Adopt MUST be the default mode; replacement MUST NOT be preselected. |
| A5-R4 | lemonfiber MUST NOT move, rename, or reorganise existing media as part of migration. |
| A5-R5 | Side-by-side evaluation on alternative ports MUST be supported. |
| A5-R6 | lemonfiber MUST refuse to downgrade an existing *arr database and MUST explain why. |
| A5-R7 | An upgrade of an existing *arr database MUST require a backup first. |
| A5-R8 | Items that could not be migrated MUST be enumerated explicitly. |
| A5-R9 | A failed or abandoned migration MUST leave the pre-existing setup functional. |
| A5-R10 | An existing layout that breaks hardlinks MUST be reported with its cost quantified, and a remedy offered but not forced. |
| A5-R11 | Port conflicts with existing services MUST be detected before any plan is proposed. |
| A5-R12 | Unsupported existing components MUST be named as unsupported rather than silently ignored. |
| A5-R13 | Reading what a replacement would stop MUST answer with an offer naming what it would stop; the replacement MUST take that offer as its yes, alike at the command line and over the web API (ARCH-R48), MUST build it again from what is running when it acts, and MUST be refused, naming what moved, where the two differ (N7-R18). |
| A5-R14 | The survey’s migration envelope MUST carry, for each service it found, where that service’s configuration is read from and where its library is, or MUST say that either could not be read, so that no surface works them out from a service’s name or ports (A5-R2, N7-R11). |
Related
- A2 Setup wizard — the path when nothing exists
- A4 Reconfiguration — changing decisions post-migration
- C5 Storage management — the hardlink assessment
- E3 Backup & restore — the pre-migration safety net, which captures the existing setup’s own paths rather than lemonfiber’s
- F1 Customisation — for operators keeping their own compose file