Skip to main content
Data migration scripts live in backend/scripts/data_migrations/. They handle one-off corrections, backfills, or clean-ups that cannot be expressed as a zero-downtime Alembic migration.
This page covers how to run the scripts. The rationale for each individual migration — the problem it fixes and what it changes, plus any relevant details such as conflict handling or idempotency — lives in the module docstring at the top of the corresponding script in backend/scripts/data_migrations/. Read that before running one.

How they run

Recent migrations are wired into the container startup script (backend/scripts/start/app.sh) and run automatically on every deploy, after Alembic migrations are applied. They are idempotent (no-op once the data is corrected) and non-fatal - a failure logs a warning and retries on the next startup. This automatic execution is temporary. Each entry in app.sh is gated by a TODO: Remove this after ~<date> comment and is deleted once existing deployments have had time to upgrade. After that point the script is no longer invoked on startup. The practical consequence: if you upgrade Open Wearables long after a migration shipped, the corresponding script may already have been removed from app.sh and will not run automatically. In that case, run it manually (see below). Scripts that were never added to app.sh must always be run manually.

Run-once scripts

Some scripts are expensive to run even when there is nothing left to fix, because checking means scanning a large table. These run at most once per instance: a clean run writes a row with the script’s name to the data_migration table, and from then on the script prints already completed, skipping and exits, both on startup and when run manually. A run that fails or is interrupted writes nothing, so it is retried on the next startup. When several replicas start at once, only one runs the script; the others skip it. To run such a script again, delete its row first:
--dry-run never writes the row.

Prerequisites

The scripts import from the app package and connect to the database via the environment variables in backend/config/.env. You need:
  • Docker Compose services running (docker compose up -d)
  • The app container healthy (check with docker compose ps)
  • A database with the latest Alembic migrations applied (docker compose exec app uv run alembic upgrade head)

Running a script

All scripts are run inside the app container using uv run:

Dry run first

Every script supports a --dry-run flag. Always run it first — it prints the rows that would be affected without making any changes:

Apply changes

Once satisfied with the dry-run output, run without the flag:
Scripts write directly to the database. There is no automatic rollback. Take a database snapshot before running against production.