Skip to content

fix(deploy): wait for teal-db instead of failing on a transient state - #64

Merged
dotMavriQ merged 1 commit into
mainfrom
fix/deploy-guard-db-wait
Jul 20, 2026
Merged

fix(deploy): wait for teal-db instead of failing on a transient state#64
dotMavriQ merged 1 commit into
mainfrom
fix/deploy-guard-db-wait

Conversation

@dotMavriQ

@dotMavriQ dotMavriQ commented Jul 20, 2026

Copy link
Copy Markdown
Owner

Why

Production has been running a6c05be (Tailwind 4) since 2026-07-07, missing #57 (Vite 8) and #61 (per-user encrypted API keys). The deploy of ce6c7a0 failed 19s in and nobody noticed — main has been red since.

Diagnosis (from backup timestamps, host image list and container state on gerty):

  • quality, tests, rector all passed; only Deploy to gerty failed.
  • The image built and pushed to GHCR fine, and is present on the host.
  • Pre-flight passed and the DB backup completed (teal-20260707-083549.sql.gz, valid).
  • That leaves the destructive-migration guard, which today passes on demand against the same image.

The migration it was guarding is purely additive:

create table "user_api_keys" (...)
alter table "user_api_keys" add constraint ... foreign key ...
alter table "user_api_keys" add constraint ... unique ...

Likely trigger: a cancelled CI run took its own backup at 08:33:46 and was killed mid-restart. The next run's guard ran ~90s later while the stack was still recreating, could not reach the database, and correctly refused to go live. Two deploys colliding, surfaced as an unverifiable migration.

What changed

concurrency: cancel-in-progress: false prevents two deploys running at once, but nothing stops a cancelled deploy from leaving the box mid-apply and poisoning the next one.

  • Pre-flight now waits up to DB_WAIT_TIMEOUT (60s, overridable) for teal-db instead of checking once.
  • The same wait runs immediately before the migration scan. Minutes pass pulling the image, and that guard's failure mode is to abort the deploy — a transient connection problem must not masquerade as an unsafe migration.

A healthy database costs zero extra time.

Not changed

The destructive-migration regex, no-backup-no-deploy, and the rollback path all behaved correctly and are untouched. The guard halting on something it could not verify was right; it just had a bad reason to be unable to verify.

Verification

Summary by CodeRabbit

  • Bug Fixes
    • Improved deployment reliability by waiting for the database to become healthy before continuing.
    • Added a configurable timeout for database readiness checks, defaulting to 60 seconds.
    • Rechecks database health before migration safety validation to reduce misleading deployment failures.

The deploy of ce6c7a0 (#61) aborted 19s in, after a good backup and a
successful image pull, leaving production on a6c05be for two weeks. The
migration it was guarding is purely additive — create table, add foreign
key, add unique constraint — and the guard passes on demand today.

The likely trigger: a cancelled CI run took its own backup at 08:33:46 and
was killed mid-restart; the next run's migration guard ran 90s later while
the stack was still recreating, could not reach the database, and correctly
refused to go live. Two deploys colliding, reported as an unverifiable
migration.

Pre-flight now waits up to DB_WAIT_TIMEOUT (60s) for teal-db rather than
checking once, and the check is repeated immediately before the migration
scan — minutes pass pulling the image, and that guard's failure mode is to
abort the deploy, so a transient connection problem must not masquerade as
an unsafe migration. A healthy database still costs zero extra time.
@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

.coderabbit.yaml has a parsing error

The CodeRabbit configuration file in this repository has a parsing error and default settings were used instead. Please fix the error(s) in the configuration file. You can initialize chat with CodeRabbit to get help with the configuration file.

💥 Parsing errors (1)
Validation error: Too big: expected string to have <=250 characters at "tone_instructions"
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
📝 Walkthrough

Walkthrough

docker/deploy-prod.sh adds a configurable database wait timeout, polls teal-db until healthy during pre-flight, and repeats the readiness check before scanning destructive migrations.

Changes

Database readiness

Layer / File(s) Summary
Health polling and migration readiness gate
docker/deploy-prod.sh
Adds DB_WAIT_TIMEOUT with a 60-second default, polls teal-db health until healthy or timed out, and repeats the wait before destructive-migration scanning.

Estimated code review effort: 2 (Simple) | ~10 minutes

Sequence Diagram(s)

sequenceDiagram
  participant deploy-prod.sh
  participant wait_for_db
  participant Docker
  participant teal-db
  deploy-prod.sh->>wait_for_db: Wait for database readiness
  wait_for_db->>Docker: Inspect teal-db health
  Docker->>teal-db: Read health status
  teal-db-->>Docker: Return health status
  Docker-->>wait_for_db: Return health result
  wait_for_db-->>deploy-prod.sh: Continue or abort on timeout
  deploy-prod.sh->>wait_for_db: Re-check before migration scan
Loading

Poem

I’m a rabbit with a watch,
Waiting by the database hatch.
Poll, poll, healthy green—
Then migrations may be seen!
Timeout keeps the hop on track.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: waiting for teal-db during deploys instead of aborting on transient states.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/deploy-guard-db-wait

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
No server is currently available to service your request. Sorry about that. Please try resubmitting your request and contact us if the problem persists.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
docker/deploy-prod.sh (1)

65-65: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Capture the timeout status directly instead of discarding and re-querying.

Currently, wait_for_db echoes the failing status on timeout, but >/dev/null discards it, forcing the fatal message to re-query db_health. This could log an inaccurate status (e.g., healthy) if the database recovers immediately after the timeout. Capturing the output avoids this race condition. Additionally, consider adding a brief log statement before these operations to indicate that the script is waiting.

  • docker/deploy-prod.sh#L65-L65: change to final_status=$(wait_for_db) || fatal "... (status: ${final_status}) ..." and optionally add a log statement beforehand.
  • docker/deploy-prod.sh#L108-L108: change to final_status=$(wait_for_db) || fatal "... (status: ${final_status}) ..." and optionally add a log statement beforehand.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docker/deploy-prod.sh` at line 65, Capture the output of wait_for_db in
final_status instead of discarding it and re-querying db_health, then use that
captured status in the fatal message at docker/deploy-prod.sh lines 65-65 and
108-108. Optionally add a brief log before each wait indicating that the script
is waiting for the database.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@docker/deploy-prod.sh`:
- Line 65: Capture the output of wait_for_db in final_status instead of
discarding it and re-querying db_health, then use that captured status in the
fatal message at docker/deploy-prod.sh lines 65-65 and 108-108. Optionally add a
brief log before each wait indicating that the script is waiting for the
database.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b3ca995d-0a94-45a3-95ce-1a619346abef

📥 Commits

Reviewing files that changed from the base of the PR and between ce6c7a0 and 40f28d2.

📒 Files selected for processing (1)
  • docker/deploy-prod.sh

@dotMavriQ
dotMavriQ merged commit 1cbe798 into main Jul 20, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant