Migration from Upstream

Bazarr+ is a drop-in replacement for upstream Bazarr. It uses a compatible database with additional columns added on first run, the same config files, and the same volume paths. Switching takes about five minutes.

The v2.4 additions are additive and non-destructive. Your existing settings, language profiles, providers, and the shared OpenSubtitles-compatible token all carry over. Schema migrations run automatically on first boot, and the old shared compat token is auto-seeded as an "Unlimited Default" key in the new Distribution Hub, so existing integrations keep authorizing without changes.

Back up your config

Before making any changes, stop Bazarr and copy your entire config directory. This is the directory you mounted as /config in your existing Bazarr container. Stop it first: Bazarr writes to its SQLite database while it runs, and a copy taken in the middle of a write may not open later.

Shell
BACKUP=./config-backup-$(date +%Y%m%d-%H%M%S)
if [ -e "$BACKUP" ] || [ -e "$BACKUP.partial" ]; then
  echo "$BACKUP or $BACKUP.partial already exists, so nothing was stopped or copied"
elif docker compose down; then
  cp -a ./config "$BACKUP.partial" &&
  mv "$BACKUP.partial" "$BACKUP" &&
  echo "Backup: $BACKUP"
else
  echo "Bazarr did not stop, so nothing was copied"
fi

The copy only runs if Bazarr stopped. If docker compose down reports an error, fix that first and run the commands again. Each run makes a new folder named with the date and time, so a second backup never lands inside the first, and you can restore from it if anything goes wrong. The config directory contains your SQLite database (db/bazarr.db), settings, logs, and cached data. The last line prints the folder name, for example config-backup-20260924-143015. Note it: the revert steps below ask for it. If the copy fails partway, for example on a full disk, that line is missing and the unfinished copy is left in a folder ending in .partial. It is not a backup: remove it once you have fixed the cause, then run the commands again.

Do not skip this step. While the migration is straightforward and non-destructive, having a backup means you can always go back.

Container swap process

Bazarr is still stopped from the backup step. The actual swap is just changing the Docker image name in your compose file.

  1. Update the image in your docker-compose.yml. Change the image line from the upstream image to the Bazarr+ image:
    Before → After
    # Before (upstream)
    image: lscr.io/linuxserver/bazarr:latest
    # or
    image: ghcr.io/morpheus65535/bazarr:latest
    
    # After (Bazarr+)
    image: ghcr.io/lavx/bazarr:latest
  2. Add the companion services. The only companion service recommended now is FlareSolverr, plus the optional AI translator. OpenSubtitles.org is a native Provider Hub plugin in v2.4: install it from the Marketplace, then point its FlareSolverr URL setting at the flaresolverr service below (http://flaresolverr:8191/v1 on a shared Docker network, or http://localhost:8191/v1 with host networking, in which case also publish FlareSolverr's 8191:8191 port or run it on host networking too so 8191 is reachable on the host). There is no separate scraper container anymore.
    docker-compose.yml additions
    # Add these services:
      # Recommended for the OpenSubtitles.org and many other plugins. Set this container's
      # /v1 URL (http://flaresolverr:8191/v1) in the plugin's FlareSolverr URL. With host
      # networking use http://localhost:8191/v1 and add a "8191:8191" ports mapping below
      # (or run flaresolverr on host networking too) so 8191 is reachable on the host.
      flaresolverr:
        image: ghcr.io/flaresolverr/flaresolverr:latest
        container_name: flaresolverr
        restart: unless-stopped
        environment:
          - LOG_LEVEL=info
        healthcheck:
          test: ["CMD", "curl", "-sf", "http://localhost:8191/health"]
          interval: 30s
          timeout: 10s
          retries: 3
          start_period: 60s
    
      # AI translation via OpenRouter (free tiers available)
      # Configure the API key in Settings > AI Translator
      ai-subtitle-translator:
        image: ghcr.io/lavx/ai-subtitle-translator:latest
        container_name: ai-subtitle-translator
        restart: unless-stopped
        ports:
          - 8765:8765
        healthcheck:
          test: ["CMD", "curl", "-sf", "http://localhost:8765/health"]
          interval: 30s
          timeout: 10s
          retries: 3
          start_period: 10s
  3. Start the new stack.
    Shell
    docker compose up -d

That is it. Open Bazarr+ in your browser and everything should be where you left it.

What changes

After the swap, you get all the Bazarr+ additions on top of your existing setup:

  • Provider priority. A toggle in Settings > Subtitle Sources enables priority-ordered querying with early stop.
  • Bulk operations. Series and Movies pages gain checkboxes and a batch action toolbar.
  • Subtitle editor. A new "Edit" button appears next to each downloaded subtitle.
  • AI translation. Translate subtitles into any language via OpenRouter models. Configure in Settings > AI Translator.
  • Telemetry removed. All Google Analytics tracking code is gone. No data leaves your server.
  • UI improvements. Navigation changes, keyboard shortcuts, and various polish throughout.

What stays the same

  • Database. Your SQLite database is used as-is. Bazarr+ adds a few columns via Alembic migrations on first run (e.g. score_out_of, tvdbId), but these do not break backwards compatibility.
  • Settings. All your provider accounts, language profiles, Sonarr/Radarr connections, and notification settings carry over.
  • History. Your entire download and upgrade history remains intact.
  • Subtitles. All previously downloaded subtitle files stay on disk, untouched.
  • Scheduled tasks. Your scan intervals and other scheduled tasks continue as configured.
  • OpenSubtitles-compatible token. The shared compat token from earlier versions is auto-seeded as an "Unlimited Default" key in the Distribution Hub, so any Jellyfin, VLSub, or custom client using it keeps working unthrottled.

External Integration has moved

One item changed location in v2.4. The old Settings > External Integration page is gone. Endpoint and token management for the OpenSubtitles-compatible API now lives in the Distribution Hub, and /settings/external redirects there. If you bookmarked or scripted against the old page, point it at the Distribution Hub instead. To recover the current shared token, use the "Reveal token" action on the Unlimited Default key.

Password upgrade notice

Upstream Bazarr stores passwords using MD5, which is insecure. On first login after the migration, Bazarr+ will transparently upgrade your password hash to PBKDF2 with 600,000 iterations. This happens automatically when you enter your existing password. No action needed on your part.

Note: If you later revert to upstream Bazarr by restoring the backup you made before migrating, your old password hash comes back with it and nothing needs resetting. Only a revert without that backup needs a password reset, because upstream cannot read the PBKDF2 hash. See the section below for details.

Reverting if needed

If you want to switch back to upstream Bazarr for any reason:

  1. Stop Bazarr+ and restore your backup. Run ls -d ./config-backup-* to list your backups and put the exact folder you made before migrating in BACKUP. A name ending in .partial is a copy that did not finish, and the commands refuse it. The commands check that folder exists, stop Bazarr+, copy the backup next to your config, and only then swap it in. If Bazarr+ does not stop, nothing is copied or moved. Your current config is moved to ./config-bazarr-plus-YYYYMMDD-HHMMSS, not deleted: the name carries the time of the restore, and the last line prints where the config went, so reverting a second time never has anything to move out of the way first. If any step fails, the config you had stays where it is. If ./config-restore is already there, from an interrupted restore, the commands stop before anything else happens and delete nothing. Move that folder somewhere else, then run them again.
    Shell
    BACKUP=./config-backup-YYYYMMDD-HHMMSS   # your backup folder's exact name
    MOVED=./config-bazarr-plus-$(date +%Y%m%d-%H%M%S)
    if [ ! -d "$BACKUP" ]; then
      echo "No backup folder at $BACKUP"
    elif [ "${BACKUP%.partial*}" != "$BACKUP" ]; then
      echo "$BACKUP is a copy that did not finish, so nothing was restored"
    elif [ -e ./config-restore ]; then
      echo "./config-restore is already there, so nothing was restored. Move it away, then run the commands again"
    elif docker compose down; then
      cp -a "$BACKUP" ./config-restore &&
      mv ./config "$MOVED" &&
      mv ./config-restore ./config &&
      echo "Your config is now the backup; the config you had is at $MOVED"
    else
      echo "Bazarr+ did not stop, so nothing was restored"
    fi
  2. Change the image back to the upstream image in your compose file.
  3. Start it. Run docker compose up -d.

The backup puts everything back as it was before the migration, including your old password, so there is nothing to reset. Changes you made in Bazarr+ after migrating do not come back with it.

If you did not keep a backup and want to revert, there are a few things to be aware of. The password hash in config/config.yaml inside your config directory (./config/config/config.yaml in the commands above) under auth.password will be in PBKDF2 format, which upstream cannot read. Clear the password field to reset it. Additionally, the series_scores and movie_scores config sections are deleted by Bazarr+, gemini_key is migrated to a gemini_keys list, and the analytics section is removed.

Next steps