feat(deploy): run database migrations automatically and ordered on deploy (#89) #162

Merged
claude-bot merged 2 commits from feat/89-auto-migrate-service into main 2026-07-16 00:13:43 +00:00
Contributor

Summary

The documented upgrade flow required a separate manual make migrate-safe after docker compose up, and nothing enforced ordering — backend/worker gated only on db/redis health, so they booted and served against the old schema until the operator remembered to migrate. Fresh first-boot also required the manual step before the app worked at all.

Changes

  • docker-compose.yml: new one-shot migrate service — backend image, command: alembic upgrade head, restart: "no", depends_on: db (service_healthy). backend, worker, and beat now also depends_on: migrate (service_completed_successfully).
  • docker-compose.dev.yml: overrides migrate to the backend-dev target with the ./webapp/backend bind mount, so a freshly-written migration is applied on up from live source.
  • Docs: README.md, docs/OPERATIONS.md, webapp/CLAUDE.md — routine update is now just git pull && docker compose up -d --build.

Behaviour

  • Idempotent: creates the schema on a fresh DB (first-boot now works with no manual step), applies only pending revisions on an existing DB, no-op otherwise.
  • Ordered & safe: if the migration fails, migrate exits non-zero and app services do not start — old containers keep running rather than serving a half-migrated schema.
  • Alembic connects as questboard_migrate via DATABASE_MIGRATE_URL (env.py), unchanged.

Decision: no auto-backup / no auto-preflight

The automatic path deliberately does not run db_preflight or take a pre-migration dump — db_preflight by design rejects an empty DB, which would block fresh first-boot. make migrate-safe remains the guarded preflight + backup + migrate flow, documented for major upgrades.

Verification

  • docker compose config and docker compose -f … -f docker-compose.dev.yml config both resolve cleanly; backend shows migrate: service_completed_successfully in the merged config.
  • Command is identical to the existing (working) make migrate target, just run as an ordered one-shot.

Closes #89

🤖 Generated with Claude Code

## Summary The documented upgrade flow required a **separate manual** `make migrate-safe` after `docker compose up`, and nothing enforced ordering — `backend`/`worker` gated only on db/redis health, so they booted and served against the **old schema** until the operator remembered to migrate. Fresh first-boot also required the manual step before the app worked at all. ## Changes - **`docker-compose.yml`**: new one-shot `migrate` service — backend image, `command: alembic upgrade head`, `restart: "no"`, `depends_on: db (service_healthy)`. `backend`, `worker`, and `beat` now also `depends_on: migrate (service_completed_successfully)`. - **`docker-compose.dev.yml`**: overrides `migrate` to the `backend-dev` target with the `./webapp/backend` bind mount, so a freshly-written migration is applied on `up` from live source. - Docs: `README.md`, `docs/OPERATIONS.md`, `webapp/CLAUDE.md` — routine update is now just `git pull && docker compose up -d --build`. ## Behaviour - **Idempotent**: creates the schema on a fresh DB (first-boot now works with no manual step), applies only pending revisions on an existing DB, no-op otherwise. - **Ordered & safe**: if the migration fails, `migrate` exits non-zero and app services do **not** start — old containers keep running rather than serving a half-migrated schema. - Alembic connects as `questboard_migrate` via `DATABASE_MIGRATE_URL` (env.py), unchanged. ## Decision: no auto-backup / no auto-preflight The automatic path deliberately does **not** run `db_preflight` or take a pre-migration dump — `db_preflight` by design rejects an empty DB, which would block fresh first-boot. `make migrate-safe` remains the guarded preflight + backup + migrate flow, documented for major upgrades. ## Verification - `docker compose config` and `docker compose -f … -f docker-compose.dev.yml config` both resolve cleanly; `backend` shows `migrate: service_completed_successfully` in the merged config. - Command is identical to the existing (working) `make migrate` target, just run as an ordered one-shot. Closes #89 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(deploy): run database migrations automatically and ordered on deploy (#89)
Some checks failed
CI / Backend migration, tests, and audit (pull_request) Failing after 2s
CI / Docker image build (pull_request) Successful in 14s
CI / Frontend tests, audit, and build (pull_request) Successful in 1m30s
CI / Bot tests and audit (pull_request) Successful in 2m21s
CI / Backend lint (ruff) (pull_request) Successful in 3m3s
e76768adcd
The documented upgrade flow required a separate manual `make migrate-safe`
after `docker compose up`, and nothing enforced ordering: backend/worker
gated only on db/redis health, so they booted and served against the old
schema until the operator remembered to migrate. Fresh first-boot also
required the manual step before the app worked at all.

Add a one-shot `migrate` service (backend image, `alembic upgrade head`,
`restart: "no"`, depends on db healthy) and make backend, worker, and beat
`depends_on` it with `condition: service_completed_successfully`. Alembic
connects as questboard_migrate via DATABASE_MIGRATE_URL (env.py). The
command is idempotent: creates the schema on a fresh DB, applies only
pending revisions on an existing one, no-op otherwise.

The dev overlay overrides `migrate` to the backend-dev target with the
source bind mount, so a freshly-written migration is applied on `up`.

The automatic path intentionally does NOT run db_preflight or take a
pre-migration backup (db_preflight by design rejects an empty DB, which
would block fresh first-boot). `make migrate-safe` remains the guarded
preflight+backup+migrate flow for major upgrades. Docs updated in
README, OPERATIONS.md, and webapp/CLAUDE.md.

Closes #89

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
claude-bot scheduled this pull request to auto merge when all checks succeed 2026-07-15 23:01:08 +00:00
ci: re-trigger (previous run hit an infra flake)
All checks were successful
CI / Backend lint (ruff) (pull_request) Successful in 34s
CI / Docker image build (pull_request) Successful in 10s
CI / Frontend tests, audit, and build (pull_request) Successful in 1m55s
CI / Bot tests and audit (pull_request) Successful in 6m15s
CI / Backend migration, tests, and audit (pull_request) Successful in 12m3s
0035d0dba2
claude-bot deleted branch feat/89-auto-migrate-service 2026-07-16 00:13:43 +00:00
Sign in to join this conversation.
No description provided.