Skip to content

Move to a new server

There are two ways to move to new hardware:

  • Move the whole server. Copy your files and restore a database backup on the new machine. See Backup and restore. If the media folder ends up at a different path inside the container, see Change the media location below.
  • Move one person’s library to a different, already running server. That’s what this page covers.

A library move uses the migrate command of the Frameleaf command-line tool. It runs on a computer you control, copies everything over the API, and ends with an audit you review before you retire the old server.

The web app never runs a migration, never asks for API keys and never deletes anything. Nothing is ever deleted from the old server automatically.

  1. Preflight checks both connections, the API key permissions and the owner on each side. It changes nothing.
  2. Dry run lists what would move and what the new server already has. It writes nothing.
  3. Run copies originals, then albums (with their collections), tags, stacks and people. Every finished item is saved to a local ledger file, so an interrupted run resumes where it stopped.
  4. Retry tries failed originals again, adds them to their albums and tags, and reattaches names to faces.
  5. Verify checks every original on the new server by checksum, without contacting the old one, and writes an audit report.

Move one person at a time. You need an API key for that person on both servers. A key can only read and write its own account’s library, so an administrator’s key would copy the photos into the administrator’s account.

Sign in as the person on each server, open Account settings, then API keys, and create a key with these permissions (or all):

Server Required Optional
Old asset.read, asset.download, album.read, tag.read stack.read, person.read
New asset.upload, asset.update, album.create, albumAsset.create, tag.create, tag.asset stack.create, person.create, person.reassign, face.read

Without the optional permissions, stacks and people are skipped and everything else still moves. If the account doesn’t exist on the new server yet, create it first.

From a checkout of the Frameleaf source, on the computer that will run the move:

Terminal window
pnpm install --frozen-lockfile
pnpm --filter @immich/sdk build
pnpm --filter @immich/cli build

The computer needs network access to both servers and only a little free disk: each original is streamed to a temporary folder next to the ledger and removed as soon as it’s uploaded.

Type each key when the terminal waits for it. Nothing is shown as you type, and the key stays out of your shell history. The variable names are the tool’s own and can’t be changed, and no command takes a key as an argument.

Terminal window
export FRAMELEAF_FROM_URL=https://old-server.example/api
export FRAMELEAF_TO_URL=https://new-server.example/api
read -rs FRAMELEAF_FROM_KEY && export FRAMELEAF_FROM_KEY
read -rs FRAMELEAF_TO_KEY && export FRAMELEAF_TO_KEY
Terminal window
node packages/cli/dist/index.js migrate --preflight --ledger ./library-move.sqlite
node packages/cli/dist/index.js migrate --dry-run --ledger ./library-move.sqlite

Preflight stops on any missing required permission and warns if the two accounts have different email addresses (everything ends up owned by the new server’s account). It doesn’t create a ledger. If an existing ledger was made for a different pair of servers, use a different --ledger path.

The dry run fills the ledger with what’s on the old server and asks the new one which originals it already has. Its report is marked as a dry run and never shows a pass.

Terminal window
node packages/cli/dist/index.js migrate --ledger ./library-move.sqlite --serve

--serve adds a progress page at http://127.0.0.1:2285 where you can pause, resume or stop. Closing the page doesn’t stop the move.

If the run stops for any reason (Ctrl+C, a reboot, a dropped connection), run the same command again. The ledger skips everything already done. Keep the ledger file until you’ve retired the old server.

Albums, tags and stacks are only marked complete once every one of their photos is on the new server. If an original failed, its albums stay pending until it arrives.

Fix the cause first (a damaged or missing original on the old server, a timeout), then:

Terminal window
node packages/cli/dist/index.js migrate --ledger ./library-move.sqlite --retry-failed
Terminal window
node packages/cli/dist/index.js migrate --verify --ledger ./library-move.sqlite

This needs only the FRAMELEAF_TO_* variables. It rechecks every original in the ledger against the new server and writes library-move.sqlite.audit.json, and refuses a server other than the one the ledger was written for. Photos added to the old server after the run aren’t included; run step 4 again to pick them up, then verify.

A ledger used only for dry runs never verifies as a pass, and --verify takes no other options. The exit status is 0 only for a pass, 2 for anything else and 1 for an error, so you can script it.

On the new server, open Settings, then Storage & originals, then Move or export your library, and choose Prepare migration checklist. Continue through Source and Preflight to Review, choose Open audit report and select the audit file. It’s read in your browser only. It isn’t uploaded or saved, and closing the report discards it.

Section What it shows
Verdict Pass only when every original was checked, none is missing or failed, and every one was verified. Incomplete, Audit incomplete (the audit was stopped) and Dry run are never a pass
Owners The account copied from and the account that owns everything on the new server
Originals Transferred (the ledger records it on the new server) and Verified by checksum (found there by this audit). Only verified originals count
Albums Albums complete out of the total, top level or inside a collection
Tags, people, stacks Tags assigned, names attached to faces and stacks recreated, each out of the total
Physical references New uploads, originals that matched a file the new server already had, and Live Photo pairs linked
Unresolved items Every original, album, tag, stack or person still outstanding, with its reason. Filter by type or name

The report is refused if it contains credentials or file paths from either computer, if its counts contradict each other or its verdict, or if it isn’t a current audit report. Run the verify command again for a clean file.

Retire it only when:

  • the latest report shows Pass
  • every unresolved item is fixed or accepted
  • you have a backup of the new server
  • you’ve kept the ledger file and the final report

Album sharing, activity, comments and anything owned by another account aren’t moved. Thumbnails, faces, smart search and places are rebuilt by the new server’s own jobs.

If you move a whole server and the media folder is mounted at a different path inside the container than before, the database still holds the old paths. The change-media-location command rewrites them:

Terminal window
docker compose exec -it immich-server frameleaf-admin change-media-location

It asks for the previous and the new value of FRAMELEAF_MEDIA_LOCATION (for example /data and /my-data), shows the change and asks you to confirm. Then set FRAMELEAF_MEDIA_LOCATION to the new value and restart. Back up the database first. See Server commands.