Skip to content

feat(migrations): optionally run migrations on app startup - #537

Open
jlav wants to merge 2 commits into
mainfrom
jl/migrate-on-startup
Open

jlav wants to merge 2 commits into
mainfrom
jl/migrate-on-startup

Conversation

@jlav

@jlav jlav commented Sep 26, 2026 •

Copy link
Copy Markdown
Member

HUMAN:

  • A human has tested these changes.

AGENT:


Why

We're trying to make our systems simpler to deploy. Today the app never migrates its own database, so every install needs one more step to run alembic upgrade head. That's either a command someone runs by hand, or an init container or separate workload in the deployment.

This adds an opt-in flag so the app can safely migrate its database when it starts.

Summary

  • RUN_MIGRATIONS_ON_STARTUP=true (or 1) makes the server run alembic upgrade head in its lifespan before it serves anything. A failed migration stops startup. It's off by default.
  • Replicas and workers can start at the same time. The advisory lock migrations/env.py already takes makes them wait their turn, and the waiters find the database at head.
  • migrations/env.py is now safe to run inside the app process. It keeps the caller's logging when asked to, and it opens one unpooled connection per run and closes it afterwards. A pooled connection would keep holding the advisory lock and block every other replica.

Issue Number

How to Test

uv run pytest tests/unit/test_saas_lifespan.py

This covers the flag, the startup order, an in-process migration of an empty database, the app and a separate alembic process migrating at the same time, and a failing migration.

To see it end to end, point the app at an empty database:

RUN_MIGRATIONS_ON_STARTUP=true DB_HOST=127.0.0.1 DB_NAME=<empty database> \
  uv run uvicorn openhands.server.listen:app --workers 2

Both workers log database_migrations_starting and database_migrations_succeeded, each revision runs once, and alembic_version ends at head.

Video/Screenshots

N/A

Type

  • Bug fix
  • Feature
  • Refactor
  • Breaking change
  • Docs / chore

Notes

  • The helm chart's migrate-db init container keeps working as it does today. Installs that turn this flag on can set databaseMigrations.migrate: false and drop it.
  • A replica waiting on the lock isn't serving yet, so startup probes need to allow for the longest migration.

Enterprise server image for this PR:

ghcr.io/openhands/enterprise-server:sha-85fb220

@github-actions

Copy link
Copy Markdown

⚠️ This PR contains migrations. Please synchronize before merging to prevent conflicts.

@github-actions github-actions Bot added the type: feat A new feature label Sep 26, 2026
@jlav
jlav marked this pull request as ready for review September 26, 2026 13:39
@github-actions

Copy link
Copy Markdown

Coverage report

Click to see where and how coverage changed

FileStatementsMissingCoverageCoverage
(new stmts)
Lines missing
  migrations
  env.py 1-5, 17-23, 40-97, 135-146
  server/app_lifespan
  saas_app_lifespan_service.py
Project Total  

This report was generated by python-coverage-comment-action

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type: feat A new feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant