Skip to content

Migrating to a new machine

Moving a whole Longbox install from one host to another — a failing disk, a better box, a move from a laptop to a real server.

This is written for bare metal to bare metal (systemd on both ends), because that is the case with sharp edges. Docker is a shorter version of the same thing and is covered at the end.

Read this before you start copying

Two things catch almost everyone, and both are silent — nothing errors, the library just comes up subtly wrong.

  1. Some settings are not in /etc/longbox.env. SMTP and OAuth credentials set through the admin pages live in the database, which takes precedence over the environment. Copy the env file alone and mail quietly stops working. See Configuration.
  2. Comic paths are stored absolutely. If the collection lands at a different path on the new host, every comic is marked missing until you run relocate.

What moves, and what does not

Move it? Why
<data>/longbox.db Yes Accounts, catalogue, reading state, ratings, every metadata edit, and the settings that are not in the env file. Irreplaceable.
<data>/uploads/ Yes The only copy of anything added through the browser. Not in the default backups.
<data>/covers/ Optional Regenerates on re-import, but copying it is far faster than rebuilding thousands of thumbnails.
<data>/backups/ Optional Old snapshots. Carry them if you want the history.
/etc/longbox.env Yes Paths, ports, public URL, trusted proxies. Review it on arrival rather than copying blindly.
The comics themselves Yes Longbox only ever reads them, but it cannot catalogue what is not there.
/var/cache/longbox No Extracted pages. Rebuilt on demand; copying gigabytes of derived JPEGs achieves nothing.
longbox.db-wal, longbox.db-shm No They belong to a running database. Copying them alongside the database mixes two states together. Stop the service and they go away.
/opt/longbox No The application and its virtualenv. The installer builds both, and a virtualenv is not portable between hosts anyway.

Before you begin

Check the new host will run a version at least as new as the old one. Migrations are one-way: a database at schema v6 will not open on a release that only knows v5.

python3 -c "import sqlite3;print(sqlite3.connect('/var/lib/longbox/longbox.db').execute('PRAGMA user_version').fetchone()[0])"

Installing from the same git commit, or a newer one, satisfies this. Newer is fine — migrations run forward automatically on first start.

1. Install Longbox on the new host

Get a working, empty install running first. Proving the plumbing works before any data is involved means a later failure has only one possible cause.

git clone <your-repo> longbox && cd longbox
sudo ./deploy/install.sh

Do not register an account. The first account to register adopts an unowned library, and you are about to bring a database that already has one.

sudo systemctl stop longbox

2. Stop the old server

sudo systemctl stop longbox

Stopping first is not optional. SQLite in WAL mode spreads committed data across longbox.db and longbox.db-wal, so copying the database out from under a running server can capture a torn state.

With the service stopped the data directory is consistent and can be copied as it stands. Take a labelled snapshot as well, so there is a known-good artefact independent of the copy:

sudo longbox-cli backup --label pre-migration --include-uploads

3. Copy the data directory

sudo rsync -aHAX --info=progress2 /var/lib/longbox/ newhost:/tmp/longbox-data/

-a preserves ownership by numeric uid, which is exactly the thing to be careful about: the longbox service user almost certainly has a different uid on the new host, because both were allocated by whichever packages happened to be installed first. Do not try to make the numbers match — fix it on arrival:

sudo rsync -a /tmp/longbox-data/ /var/lib/longbox/
sudo chown -R longbox:longbox /var/lib/longbox
sudo rm -f /var/lib/longbox/longbox.db-wal /var/lib/longbox/longbox.db-shm

Removing the WAL and shared-memory files matters even after a clean stop — they are worthless on the new host and actively harmful if they are stale.

4. Copy the comics

sudo rsync -aHAX --info=progress2 /srv/comics/ newhost:/srv/comics/

Put them at the same path if you possibly can. It makes the next step unnecessary.

5. Tell the library where the comics went

Only needed if the collection landed somewhere different. Comic paths are stored absolutely, so without this every comic shows as missing — and re-importing is not the fix, because it strands reading state, ratings and metadata edits on the old rows.

sudo longbox-cli relocate --dry-run /old/path /srv/comics
sudo longbox-cli relocate /old/path /srv/comics

It rewrites comic paths, watched folders and per-account library folders in one pass, touching the database only and never a file. A rewrite that would land on a path another comic already occupies is skipped rather than forced, and anything still absent afterwards is reported rather than quietly marked present.

6. Review the configuration

Copy /etc/longbox.env across, then read it — several values describe the old host rather than the service:

sudo rsync -a oldhost:/etc/longbox.env /etc/longbox.env
sudo chmod 640 /etc/longbox.env
Setting Check
LONGBOX_BROWSE_ROOTS Do those paths exist on this host?
LONGBOX_DATA_DIR, LONGBOX_UPLOAD_DIR, LONGBOX_BACKUP_DIR If any sits outside the defaults, re-run sudo ./deploy/install.sh so the ReadWritePaths drop-in is regenerated here. Without it every write fails with EROFS on a directory that exists and is owned correctly.
LONGBOX_PUBLIC_URL Only changes if the name changes.
LONGBOX_TRUSTED_PROXIES Is the proxy still on loopback, or somewhere else now?
LONGBOX_PORT Free on this host?

7. Start it

sudo systemctl start longbox
journalctl -u longbox -f

Migrations run automatically on startup if the new host is on a newer release.

8. Verify — properly

Worth not skipping, because it can prove something much stronger than "the site loads".

curl -s localhost:8080/healthz
sudo longbox-cli list-users
sudo longbox-cli verify

verify re-reads every comic in full and compares SHA-256 against the hashes stored on the old host. A clean run is end-to-end proof that the collection arrived byte-identical — a far better answer than eyeballing the library grid. On a large collection it takes a while; it is still the right thing to run once.

Then, in a browser: open a comic and confirm it resumes at the page you were on. Reading progress is the loss people notice.

9. Move the name

Only once the above passes.

  • Point DNS, or the LAN dnsmasq entries, at the new address.
  • Move the reverse-proxy site block to the new backend — see Reverse proxy and HTTPS.
  • Update the OAuth redirect URIs in the Google, Apple and Facebook consoles if the public URL changed. External sign-in fails with a redirect-mismatch error until you do, and that error surfaces at the provider, not in Longbox's logs.

Sessions survive the move, because tokens are stored as hashes in the database and the database came along. If the host name changed everyone signs in again regardless, since cookies are scoped to the domain.

OPDS tokens survive too, so phones and e-readers keep working — provided they were pointed at a name rather than an address.

10. Decommission deliberately

Keep the old machine's data directory until you have read a few comics on the new one and taken at least one fresh backup there. The old host is the only rollback that exists.


Docker

Shorter, because the container is disposable and only the volumes matter.

docker compose stop
sudo rsync -aHAX ./data/ newhost:/srv/longbox/data/
sudo rsync -aHAX /srv/comics/ newhost:/srv/comics/

On the new host, clone the repository, restore docker-compose.yml with the same mount paths, put data/ where the compose file expects it, and:

docker compose up -d --build

Copying the image is not the migration. The image is rebuilt from the repository in seconds and holds none of your data; data/ is the migration.

Everything under What moves, and what does not still applies, including relocate if the comics mount lands at a different path inside the container. The path stored in the database is the one the container sees, not the one on the host.