backupchecks/docs/migrations.md

3.1 KiB
Raw Blame History

Database migrations policy

This document describes how database schema changes are handled for Backupchecks .

Overview

  • The baseline schema is defined in backend/app/models.py.
  • On application startup, the following is executed inside create_app():
    1. db.create_all() creates any missing tables and columns defined in the models.
    2. run_migrations() executes in-image SQL migrations from backend/app/migrations.py.

This approach allows:

  • Clean databases to be created automatically.
  • Existing databases to be upgraded in-place without manual SQL.
  • Safe repeated restarts: migrations are idempotent and can be run multiple times.

Adding migrations

When you change the schema in a way that is not automatically covered by db.create_all() (for example, altering column nullability, adding constraints, backfilling data), follow these steps:

  1. Add or adjust the corresponding model(s) in models.py.

  2. In migrations.py:

    • Add a new function, for example:

      • def migrate_xyz():
      • Perform the required SQL using db.get_engine() and sqlalchemy.text.
      • Always check the current state first (e.g. whether a column or constraint already exists).
    • Call this function from run_migrations() in the correct order.

  3. Do NOT remove older migration functions. They must remain so that:

    • Existing databases can still be upgraded from older versions.
    • New installations run all migrations but older ones become no-ops because their checks see that the changes are already applied.
  4. Each migration must be idempotent:

    • It should detect whether its change is already in place and then exit without error.
    • This allows run_migrations() to be executed on every startup.

Current migrations (initial set)

Implemented in backend/app/migrations.py:

  • migrate_add_username_to_users()

    • Adds a username column to the users table if it does not exist.
    • Backfills username from email where possible.
    • Sets username to NOT NULL.
    • Adds a UNIQUE constraint on users.username.
  • migrate_make_email_nullable()

    • Ensures the email column on users is nullable.
    • If the column is currently NOT NULL, the migration executes:
      ALTER TABLE "users" ALTER COLUMN email DROP NOT NULL.
  • run_migrations()

    • Calls the above migrations in order.
    • Logs progress to stdout so changes are visible in container / Portainer logs.
  • migrate_reporting_report_config()

    • Adds report_definitions.report_config (TEXT) if it does not exist.
    • Stores the JSON report definition for the reporting UI (selected columns, chart types, filters) so the same definition can later be reused for PDF export.

Future changes

  • Every time you introduce a non-trivial schema change, update:

    • backend/app/models.py
    • backend/app/migrations.py
    • This document (docs/migrations.md) add a short description of the new migration.
  • When sharing the repository state (for example in a ZIP), always include the current migrations.py and this document so the migration history and policy are clear.