Skip to main content

Upgrading

This guide covers how to safely upgrade OpenTranscribe between versions, including pre-upgrade preparation, the upgrade process, and rollback procedures.

Pre-Upgrade Checklist

Before upgrading, complete these steps:

  1. Back up the database -- this is non-negotiable
    # An installed deployment has no backup subcommand; dump directly.
    docker compose exec -T postgres pg_dump -U postgres opentranscribe \
    > "opentranscribe-backup-$(date +%Y%m%d-%H%M%S).sql"
    (In a git clone of the repo, ./opentr.sh backup does the same thing.)
  2. Note your current version
    ./opentranscribe.sh version
    # or, without the script:
    curl -s http://localhost:5174/api/version
    curl -s http://localhost:5174/api/health | python3 -m json.tool
  3. Read the changelog for the target version at CHANGELOG.md
  4. Check for breaking changes -- major version bumps or migration notes
  5. Test in staging first if you have a staging environment
danger

Always back up your database before upgrading. Database migrations run automatically on startup and cannot be undone without a backup.

Standard Upgrade Process

# Pull the newest images for your pinned version and restart
./opentranscribe.sh update

# Move to a specific release
./opentranscribe.sh update --version v0.5.0

# Also refresh compose files, scripts and the .env template
./opentranscribe.sh update-full
opentranscribe.sh, not opentr.sh

opentranscribe.sh is the management script the installer places next to your compose files — it is what you have. opentr.sh is the development script and only exists in a git clone of the repository. This page previously said ./opentr.sh update, which no installed deployment can run.

update pulls images and restarts in phases, polling the backend's own /health rather than letting Compose decide when the backend is ready. That matters on an upgrade: running the migration chain on a populated database can take minutes, and Compose's dependency resolver would otherwise give up and terminate the backend mid-migration.

Migrations run automatically when the backend starts. When it is finished, the script prints the version that is actually running.

Manual Upgrade

# Pull new images
docker compose pull

# Restart services with new images
docker compose up -d --force-recreate

Upgrading to a Specific Version

Every service image resolves ${OT_IMAGE_TAG:-latest}, so pinning is one setting rather than an edit to each service:

# Recommended — sets OT_IMAGE_TAG in .env, then pulls and restarts in phases
./opentranscribe.sh update --version v0.5.0

# Equivalent by hand
echo 'OT_IMAGE_TAG=v0.5.0' >> .env
docker compose pull && docker compose up -d --force-recreate

A fresh install pins itself to the release it installed, so a deployment tracks a known version rather than whatever :latest happens to be. See the releases page for available versions.

Returning to the previous version

./opentranscribe.sh update --rollback

This re-pins the image tag recorded by the last update --version.

Images roll back; the database does not

The migration chain is one-way. Rolling images back does not revert schema changes, and an older image may not be able to read the newer schema. A real rollback means restoring the backup you took before upgrading. update refuses a downgrade unless you pass --rollback or --force-downgrade.

Database Migrations

How Migrations Work

OpenTranscribe uses Alembic for database schema migrations. On every backend startup, the system automatically:

  1. Detects the current database schema version
  2. Stamps untracked databases with the appropriate version
  3. Runs any pending migrations to bring the database up to date

You do not need to run migrations manually -- they execute automatically.

Current Migration Chain

The migration chain progresses through these versions:

MigrationDescription
v010Baseline schema
v020System settings
v030LDAP authentication
v031Keycloak and PKI auth
v040FedRAMP compliance fields
v050Search settings
v060Transcript overlap
v070-v073PKI security, segment constraints, status enum
v080Auth configuration
v090-v091Error categories, speaker suggestion source
v100-v120Query performance indexes
v130-v140Processing model tracking, word timestamps
v150-v170File retention, local fallback, Keycloak refresh tokens
v180-v190Speaker attributes, collection default prompts
v200-v211Schema reconciliation, groups and sharing
v220-v270Speaker clusters, auto labeling, quality metrics, ASR providers, avatars
v280-v320Upload sessions, gender fields, speaker constraints, cluster names
v330Shared configs and prompts
v340User media sources
v350Diarization disabled flag
v351AI summary settings
v352Requested Whisper model per-transcription
v353Segment unique index fix
v355Independent diarization provider settings

What to Do if Migrations Fail

If a migration fails on startup:

  1. Check the backend logs for the specific error:
    docker compose logs backend | grep -i "alembic\|migration\|error"
  2. Restore your backup if the migration left the database in a broken state:
    docker compose stop backend celery-worker
    docker compose exec -T postgres psql -U postgres -d opentranscribe \
    < opentranscribe-backup-YYYYMMDD-HHMMSS.sql
    docker compose start backend celery-worker
    (In a git clone, ./opentr.sh restore backups/<file>.sql.)
  3. Report the issue -- migration failures are bugs. File an issue with the error output.
note

All OpenTranscribe migrations use idempotent SQL (IF NOT EXISTS, DO $$ ... END $$ blocks), which means they are safe to re-run. If a migration partially completed, restarting the backend will attempt to finish it.

Rolling Back

Reverting to a Previous Version

If an upgrade causes issues, you can roll back:

# 1. Stop all services
docker compose down

# 2. Restore the database backup you made before upgrading
docker compose up -d postgres
# Wait for postgres to be ready
until docker compose exec postgres pg_isready -U postgres; do sleep 2; done
docker compose exec -T postgres psql -U postgres opentranscribe < backups/opentranscribe_backup_YYYYMMDD_HHMMSS.sql

# 3. Pull the previous version images
docker pull davidamacey/opentranscribe-frontend:vPREVIOUS
docker pull davidamacey/opentranscribe-backend:vPREVIOUS

# 4. Tag them as latest (so compose uses them)
docker tag davidamacey/opentranscribe-frontend:vPREVIOUS davidamacey/opentranscribe-frontend:latest
docker tag davidamacey/opentranscribe-backend:vPREVIOUS davidamacey/opentranscribe-backend:latest

# 5. Start all services
docker compose up -d
warning

You must restore the database backup when rolling back. Newer migrations may have altered the schema in ways incompatible with older code.

Major Version Upgrades

Major version upgrades (e.g., 0.x to 1.x) may include breaking changes that require extra steps.

Embedding Migration (v3 to v4)

When upgrading across the speaker embedding architecture change:

  • speakers_v3 uses 512-dimensional pyannote embeddings
  • speakers_v4 uses 256-dimensional WeSpeaker embeddings
  • The speakers alias automatically points to the active index

The migration runs through the Admin UI:

  1. Navigate to Admin Settings > Speaker Embeddings
  2. Start the embedding migration -- this re-extracts embeddings for all speakers
  3. Monitor progress in the migration panel
  4. Once complete, the alias swaps atomically to the new index
tip

Embedding migration can take significant time depending on the number of speakers and media files. Plan accordingly and run during a maintenance window.

Breaking Changes (v0.5.0)

These affect operators upgrading to v0.5.0 regardless of whether you call the REST API directly.

Six deployment-configuration panels now require super_admin, not admin

ASR provider, Engine configuration, Backups, Media Mirror, Watch sources, and the Redaction policy floor now require the super_admin role instead of admin. They configure how the deployment runs, and several store infrastructure credentials (S3 keys, SMB passwords, SMTP passwords) that a team-level admin has no reason to read or replace.

ACTION REQUIRED if a plain admin manages any of those six panels

Promote that account to super_admin (Settings → Users → Role → Super Admin) before upgrading, or hand the work to an existing super admin. Nothing else changes tier: user accounts, tasks, search, and speaker maintenance stay at admin. Creating additional super admins from the UI is new in this release — the role selector previously offered only user and admin.

The OIDC surface is renamed — configuration keys, routes, and the admin tab

Config keys are now oidc_*, the admin tab is OIDC, and the routes are /api/auth/oidc/login and /api/auth/oidc/callback. No identity provider needs reconfiguring (the registered redirect URI still points at the SPA's /login page), and every KEYCLOAK_* environment variable keeps working permanently — the legacy spelling even wins when both are set. Stored database configuration is renamed automatically by migration v377.

What does break: a script that writes PUT /api/admin/auth-config/keycloak, reads a keycloak_* key out of GET /api/admin/auth-config, or calls /api/auth/keycloak/{login,callback} directly. GET /api/auth/methods now reports "oidc" in methods; its keycloak_enabled field is retained for one minor release so a cached SPA bundle keeps rendering the SSO button, then removed.

POST /api/auth/token/refresh now requires the CSRF header for cookie-authenticated clients

Minting a new session from the refresh cookie alone is no longer CSRF-exempt — that's exactly what a forged cross-site request would target. Browsers are unaffected; the SPA already double-submits the token, and the CSRF cookie's lifetime was extended to match the refresh cookie's. A non-browser API client that sends cookies must now also send X-CSRF-Token; clients using Authorization: Bearer are exempt as before.

PKI_TRUSTED_PROXIES is now required whenever PKI is enabled

Header-sourced PKI authentication is refused when no trusted proxy is allow-listed, instead of being accepted with a warning. Hardened deployments already refused to start in that configuration, so this only changes development and evaluation stacks that enabled PKI through the admin UI. Set it to the address the backend sees the reverse proxy arrive from.

GET /api/auth/methods no longer always advertises local

methods previously contained "local" unconditionally. It now reflects local_enabled, so a deployment whose identity lives entirely in an external IdP reports only the methods it actually accepts. The response also gained local_enabled and allow_registration fields.

Breaking API Changes

These only affect you if you call the OpenTranscribe REST API directly — from a script, an integration, or another service. The web UI ships with each release and is always in sync.

The authoritative, always-current schema is the deployment's own OpenAPI document at /api/openapi.json (browsable at /api/docs). Check it against your client after any upgrade.

GET /api/files/{uuid}tags is now an array of objects

tags on the file-detail response used to be an array of tag name strings. It is now an array of tag objects, matching what GET /api/tags has always returned:

// Before
"tags": ["Important", "Meeting"]

// After
"tags": [
{ "uuid": "019ec90a-3f41-7aaa-8000-0000000000a1", "name": "Important", "source": "manual" },
{ "uuid": "019ec90a-3f41-7aaa-8000-0000000000a2", "name": "Meeting", "source": "auto_ai" }
]

If you were reading tags as strings, read tag.name insteadfile.tags.map(t => t.name) in JavaScript, [t["name"] for t in file["tags"]] in Python. The two patterns that break are ", ".join(file["tags"]) and "Important" in file["tags"].

uuid and name are always present. source is nullable: "manual" for a tag a user applied, "auto_ai" for one applied by auto-labeling, null for tags predating the field.

What did not change:

  • GET /api/files (the list endpoint) has no tags field, before or after.
  • GET /api/tags, POST /api/tags, GET /api/tags/unused, POST /api/tags/files/{uuid}/tags and DELETE /api/tags/files/{uuid}/tags/{tag_name} are unchanged — they already returned objects, and the delete route is still keyed by tag name.
  • Search results still carry tags as plain strings (the search index stores tag names).
  • No routes, permissions, or tag-visibility rules changed. No database migration is involved.

Other Major Upgrade Considerations

  • OpenSearch version changes: May require reindexing all data
  • Model format changes: New AI models download automatically on first use
  • Authentication changes: Review auth settings after upgrading, especially for LDAP/Keycloak configurations
  • Configuration changes: Compare your .env with .env.example to identify new required variables

Verifying the Upgrade

After upgrading, verify everything is working:

1. Check Service Health

# All containers should be running and healthy
./opentranscribe.sh status

# Or check directly
docker compose ps

2. Check Backend Logs

# Look for successful startup and migration messages
docker compose logs backend --tail=50

# Verify no migration errors
docker compose logs backend | grep -i "error\|failed\|exception" | head -20

3. Verify API Health

curl -s http://localhost:5174/api/health | python3 -m json.tool

4. Test Core Functionality

  • Log in to the web UI at http://localhost:5173
  • Verify existing transcripts are accessible
  • Search for a known transcript to confirm OpenSearch is working
  • Upload a short test file to verify the transcription pipeline

5. Check Version

Confirm the UI footer or API response shows the expected version number.

Common Upgrade Issues

Container Fails to Start

# Check logs for the failing service
docker compose logs <service-name> --tail=100

# Common fix: recreate the container
docker compose up -d --force-recreate <service-name>

Migration Lock Timeout

If the backend hangs on startup waiting for a migration lock:

# Check for stuck advisory locks in PostgreSQL
docker compose exec postgres psql -U postgres opentranscribe -c "SELECT * FROM pg_locks WHERE locktype = 'advisory';"

# Restart the backend
docker compose restart backend

Model Compatibility

New versions may require updated AI models. If transcription fails after upgrading:

# Clear the model cache to force re-download
rm -rf ${MODEL_CACHE_DIR:-./models}/huggingface/hub/
docker compose restart celery-worker

New Environment Variables

If the backend logs show warnings about missing configuration:

# Compare your .env with the latest template
diff .env .env.example

# Add any missing variables from .env.example to your .env

OpenSearch Index Incompatibility

If search stops working after an upgrade:

# Check OpenSearch health
curl -s http://localhost:5180/_cluster/health | python3 -m json.tool

# If indices need rebuilding, use the Admin UI "Reindex All" function
# Or via API:
curl -X POST http://localhost:5174/api/admin/reindex -H "Authorization: Bearer <token>"

Permission Errors on Model Cache

After upgrading, the container user (UID 1000) may not have access to cached models:

# Fix permissions
./scripts/fix-model-permissions.sh

# Or manually
sudo chown -R 1000:1000 ${MODEL_CACHE_DIR:-./models}/