SQLite to PostgreSQL Migration#
DUMB provides a guarded tool for moving an existing supported service from SQLite to DUMB-managed PostgreSQL. Open the service page and select Database Migration.
This tool is deliberately separate from postgres_enabled. For most services,
enabling PostgreSQL directly changes the database used at the next start; it
does not copy existing SQLite data. InfiniDysk additionally blocks a direct
provider switch when an existing main SQLite database is present.
Supported services#
| Service | SQLite source | PostgreSQL target | Important note |
|---|---|---|---|
| Sonarr | sonarr.db, optional logs.db |
Per-instance main/log databases | Requires Sonarr 4.0.0.615 or newer; Servarr calls migration unsupported |
| Radarr | radarr.db, optional logs.db |
Per-instance main/log databases | Requires Radarr 4.1.0.6133 or newer; Servarr calls migration unsupported |
| Lidarr | lidarr.db, optional logs.db |
Per-instance main/log databases | Requires Lidarr 1.1.2.2890 or newer; upstream guide is community/unsupported |
| Prowlarr | prowlarr.db, optional logs.db |
Per-instance main/log databases | Upstream guide calls migration unsupported |
| Whisparr | whisparr.db, optional logs.db |
Per-instance main/log databases | Follow the same guarded rehearsal and validation rules |
| Bazarr | /bazarr/data/db/bazarr.db |
bazarr by default |
The SQLite database must have been upgraded by Bazarr 1.1.5 or newer; timestamp columns require type conversion |
| Pulsarr | /pulsarr/data/db/pulsarr.db |
pulsarr by default |
DUMB runs Pulsarr's migration script in staging; migration metadata tables are recreated and are not copied |
| Seerr | /seerr/config/db/db.sqlite3 |
Per-instance database | Migration metadata is recreated by Seerr and is not copied |
| AltMount | Configured database.path |
altmount by default |
AltMount supports both providers; DUMB validates the data-only conversion against AltMount's current schema |
| InfiniDysk | Configured main db.sqlite |
infinidysk by default |
Migration requires an official stable v1.2.0-or-newer runtime whose source SQLite and staged PostgreSQL schemas exactly match DUMB's supported contract; after cutover, official releases, branches, and exact commits are allowed only at or after the recorded cutover commit; only the main store moves and auxiliary stores remain SQLite |
Services that use PostgreSQL only, such as mediastorm, Riven Backend, Zilean, and Traefik Proxy Admin, do not have a SQLite source to migrate. Services without a confirmed PostgreSQL backend are not offered the tool.
InfiniDysk migration is DUMB-managed
InfiniDysk v1.2.0+ upstream supports selecting PostgreSQL only for a fresh
installation and does not provide an in-place db.sqlite migration.
DUMB's guarded workflow is separate. Official v1.2.0 commit
8c960ffc39fc85fdf9166aafd6cb2846878ec3c2
is the audited contract baseline, not a runtime pin. DUMB accepts an
official stable v1.2.0-or-newer runtime only when the live SQLite database
and the migration-only staged PostgreSQL database exactly match that
supported contract. The newest audited database contract is InfiniDysk
v1.2.5. A missing, extra, or changed table, column, index,
trigger, foreign key, identity, function, or migration-history entry fails
preflight until DUMB is updated. The workflow requires a successful
rehearsal, imports and validates only the main store, and switches
providers only after cutover validation. metrics.sqlite, warden.db, and
usenet-migration.db remain local SQLite files.
Since InfiniDysk v1.2.6, the application writes
db-contract.json
after each successful database migration pass. DUMB validates the
infinidysk-db-v1 shape, provider, exact transient-object declaration,
top-level/main-entry agreement, and the migration count, terminal ID, and
full-history hash recomputed from the live SQLite database. It repeats the
provider and history checks against the isolated PostgreSQL target after
schema staging. A present but malformed, stale, unsafe, or mismatched file
blocks migration. A missing file remains a non-blocking warning for
pre-v1.2.6 compatibility, but DUMB still requires all of its existing exact
audited schema, migration-history, catalog, key, and full-row checks.
db-contract.json proves provenance; it does not authorize a database
history that DUMB has not audited.
A successful cutover records the full InfiniDysk commit as the PostgreSQL runtime floor. Later official release tags, branch heads, and exact commit pins are accepted only when GitHub proves that their resolved commit is the recorded cutover commit or one of its descendants. Older, diverged, and unverifiable targets fail before DUMB saves the source change or launches InfiniDysk.
The cutover commit becomes the minimum InfiniDysk revision
The runtime installed when cutover begins becomes the permanent minimum revision for that PostgreSQL deployment. DUMB will not install a release, branch head, or exact commit that predates or diverges from it. Running an older build requires first using the guarded rollback to restore SQLite; later PostgreSQL writes are not copied back into the SQLite snapshot.
If a legacy installation still needs either NzbDAV-to-InfiniDysk identity migration or the optional full namespace migration, complete that before selecting PostgreSQL or starting the database-migration workflow. DUMB blocks every namespace mode once PostgreSQL is selected: the full path rewrites the SQLite main store, while an identity rename would disconnect guarded rollback job lookup.
What DUMB automates#
The workflow:
- Confirms that the service is enabled, still configured for SQLite, and has a healthy SQLite source.
- Verifies PostgreSQL connectivity, database-creation privileges, and storage for staging plus backups.
- Creates consistent SQLite backups with SQLite's backup API and verifies each copy.
- Lets the service create its current schema in isolated PostgreSQL staging databases. For InfiniDysk, DUMB runs only the installed v1.2 backend's
--db-migrationmaintenance command with an isolated configuration path; it never boots the normal application against staging. - Imports data into that schema, converts values according to PostgreSQL target types, resets sequences, and compares every imported table's row count.
- For cutover, clones the validated application schema into the named production databases, imports a fresh cold snapshot, and enables
postgres_enabledonly after validation succeeds. - Restores the saved application configuration and SQLite mode automatically if cutover fails.
Jobs and backups remain in the original compatibility location:
/config/arr-postgres-migration/
├── jobs/
└── backups/
Existing Sonarr/Radarr job history and rollback backups therefore remain visible after upgrading. DUMB never deletes the source SQLite database.
Keep an independent backup
DUMB's job-specific recovery bundle is not a substitute for a verified backup stored outside the managed migration paths. For InfiniDysk, back up its complete configuration directory before rehearsal or cutover.
Recommended flow#
1. Run preflight#
Open Database Migration on the service page and review every check. A failure blocks the job. A warning requires review but may be expected, such as a version that cannot be detected or a target database that already contains data.
The panel shows the latest persisted job for the current service, including its service name and start time. Historical failed or completed jobs remain visible after navigation and restarts; opening a panel or running preflight does not start a migration.
Preflight does not change the service configuration.
Source schema and recovered databases#
DUMB imports columns by name, so differing column order is supported. The source must still be compatible with the installed application's PostgreSQL schema. Imports, table resets, and validation address the application tables explicitly in PostgreSQL's public schema. Before truncating those tables, DUMB checks that the source tables and importable columns exist in that schema. A missing-column error identifies the affected table and columns.
The application must also resolve its unqualified table names to those same public tables. If the database role's search_path selects a same-name table in another schema or hides the public table, DUMB blocks import before truncation and reports the mismatch. This check leaves role and server settings unchanged; review the application's database configuration before retrying.
Preflight blocks the default SQLite recovery table lost_and_found in the main database and reports a warning when it appears in an optional Arr log database. Import also rejects it if that database is selected. Preserve the original database, recovered copy, unassigned rows, and an independent backup while investigating. Removing the artifact alone does not validate the recovered application data.
SQLite recovery can alter values and types or move content between tables. Its recovery-table name can also be customized, so the default-name check does not identify every recovery result. A successful quick_check does not prove that application values or types are correct. Reconcile recovered data on a separate copy, let the application complete its own SQLite upgrades, and validate its important objects before repeating the PostgreSQL rehearsal.
For a schema or type failure, review the persisted job's error and detailed progress. Useful diagnostic information includes the affected table and column names, SQLite PRAGMA table_info column types, PostgreSQL information_schema.columns types and schema names, and search_path. Share schema metadata and the error message while keeping database rows and credentials private.
2. Run a rehearsal#
Rehearsal is required before dmbdb enables cutover. It:
- takes a consistent SQLite snapshot;
- lets the service initialize isolated PostgreSQL staging databases;
- leaves InfiniDysk on its existing SQLite runtime throughout the rehearsal (a running instance stays running and a stopped instance stays stopped);
- performs the longer import against staging;
- validates all table counts and selected high-value tables; and
- removes the staging databases.
Other supported services may need a short schema-bootstrap interruption. InfiniDysk uses its migration-only maintenance command instead because its normal v1.2 runtime starts background cleanup and integration services. Do not restart DUMB or manually start/stop the service while a job is active.
Closing the migration popup does not cancel an active job. dmbdb asks for confirmation, then keeps a Database migration running in the background indicator on the service page with current progress and an Open progress action. For InfiniDysk, the 99% finalizing stage remains active while DUMB writes the durable PostgreSQL cutover authorization; the indicator and polling remain in place until the backend publishes a terminal result. Reopen the panel at any time to resume the detailed event view. Do not restart the DUMB API or container while the job is active.
When a guarded cutover finishes, the panel displays Migration completed successfully — PostgreSQL cutover is complete. If it finishes while the popup is closed, the service page replaces the running indicator with a dismissible completion notice. This confirms that DUMB imported and validated the snapshot and switched the database configuration; still verify service health, application data, and integrations afterward.
When validation succeeds, the panel displays Rehearsal passed — ready for PostgreSQL cutover and automatically selects Cut over to PostgreSQL. The service is still using SQLite at this point. Review the safeguards, enter the confirmation text again, and click Start guarded cutover to perform the actual switch.
3. Run guarded cutover#
Cutover stops the service, creates a fresh cold backup, resets the named PostgreSQL target databases, imports and validates the snapshot, switches the application configuration, and starts the service when it was running before the job. InfiniDysk cutover is stricter: the SQLite service must already be running with healthy /health and /ready responses, and DUMB requires the PostgreSQL service to remain healthy after restart before declaring success.
Downtime depends mainly on SQLite size and storage speed.
4. Validate the application#
Check the service's important objects and integrations, then perform a harmless write such as saving an unchanged setting. For an Arr, verify libraries, files, root folders, profiles, indexers, download clients, and history. For Bazarr, verify shows/movies, providers, history, and subtitle searches. For request services, verify users, requests, settings, and integrations.
For InfiniDysk, verify its UI plus provider, queue, and history state; confirm
WebDAV or rclone access and Arr category/download-client behavior; and test
representative playback and seeking. Confirm metrics.sqlite, warden.db,
and usenet-migration.db remain healthy local files.
Keep the SQLite backup until PostgreSQL and its backup schedule have been proven with a test restore.
Arr log databases are optional#
The five Arr services have a separate logs.db. Leave Migrate the service log database too disabled unless old application log entries matter. The main database already contains application configuration, library state, and history; starting fresh for diagnostic logs shortens cutover.
The option is hidden for Bazarr, Pulsarr, Seerr, AltMount, and InfiniDysk because they have one migrated main SQLite database.
Rollback behavior#
For a completed cutover, the panel can restore the preserved application configuration, set postgres_enabled: false, and restart against the untouched SQLite database.
For InfiniDysk, rollback restores only the preserved main db.sqlite state;
the three auxiliary SQLite stores never move. DUMB blocks directly toggling
postgres_enabled off after a guarded cutover; use the migration job's
explicit rollback action so the preserved source is validated before restart.
Rollback is not reverse migration
Changes written after PostgreSQL cutover are not copied into SQLite. Roll back promptly if validation fails. A later rollback can lose settings, library changes, history, requests, or other PostgreSQL writes.
An active database-migration job is marked interrupted if the DUMB API
restarts. Do not toggle postgres_enabled, replace db.sqlite, or restore the
whole backup bundle manually. Reopen Database Migration and use the guarded
rollback action whenever the job exposes it. For InfiniDysk, an interrupted,
failed, or rollback_failed result remains visible as a red service-page
recovery banner after the popup closes or the page reloads. The banner reopens
the persisted job and does not disappear merely because background polling
reached a terminal error.
A terminal rollback_failed result has two distinct recovery meanings:
rollback.retry_safe: truemeans DUMB stopped before changing saved data. The guarded rollback remains available and may be retried after fixing the reported blocker.rollback.retry_safe: falsemeans rollback had already started changing state. DUMB treats this as manual-attention recovery and freezes InfiniDysk lifecycle/provider changes. Review the private job evidence and the precise failed recovery surface before taking a targeted action; do not blindly restore the complete bundle or force the provider toggle.
PostgreSQL backups after migration#
Application-native backups generally do not contain PostgreSQL data. Configure
scheduled pg_dump backups for every migrated database by following the
pgAdmin and pgAgent example,
including retention and restore testing. Also protect the mounted data
directory's postgres subtree (container path /data/postgres, service path
/postgres_data) with the normal backup plan.
For InfiniDysk, also retain an independent filesystem backup of its complete
configuration directory so the auxiliary SQLite stores, blobs, session.key,
and other local state are protected alongside the PostgreSQL logical backup.
Upstream references#
- Sonarr PostgreSQL setup
- Radarr PostgreSQL setup
- Lidarr PostgreSQL setup
- Prowlarr PostgreSQL setup
- Whisparr PostgreSQL setup
- Bazarr PostgreSQL database
- Pulsarr SQLite-to-PostgreSQL migration
- Seerr database configuration and migration
- InfiniDysk PostgreSQL guide
- InfiniDysk native migration tracking issue #1012