diff --git a/agents/hyperhealth/Dockerfile b/agents/hyperhealth/Dockerfile index 30ddb78f..3fc08199 100644 --- a/agents/hyperhealth/Dockerfile +++ b/agents/hyperhealth/Dockerfile @@ -46,5 +46,8 @@ EXPOSE 8090 HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=5 \ CMD curl -f http://localhost:8090/health || exit 1 -# Run migrations on container start: upgrade head, fallback to stamp if needed -CMD ["sh", "-c", "alembic upgrade head || alembic stamp 001 && exec uvicorn main:app --host 0.0.0.0 --port 8090 --workers 2"] +# Migrate once, here, before uvicorn forks its workers. Never `|| alembic stamp`: +# 001 is head, so stamping on failure marks an unmigrated DB as fully migrated and +# every later `upgrade head` becomes a no-op. On failure we exit; compose has +# restart: unless-stopped and waits for postgres to be healthy, so it retries. +CMD ["sh", "-c", "alembic upgrade head && exec uvicorn main:app --host 0.0.0.0 --port 8090 --workers 2"] diff --git a/agents/hyperhealth/main.py b/agents/hyperhealth/main.py index 5ca4dd8a..0606fa61 100644 --- a/agents/hyperhealth/main.py +++ b/agents/hyperhealth/main.py @@ -7,7 +7,6 @@ import os import sys import uuid -import subprocess from contextlib import asynccontextmanager from datetime import datetime, timedelta from pathlib import Path @@ -105,23 +104,11 @@ def _get_session_factory(): async def lifespan(app: FastAPI): log.info("hyperhealth.startup", environment=ENVIRONMENT) engine = _get_engine() - - # Run Alembic migrations on startup - try: - result = subprocess.run( - ["alembic", "upgrade", "head"], - cwd=_HERE, - capture_output=True, - text=True, - timeout=30 - ) - if result.returncode == 0: - log.info("hyperhealth.migrations_completed") - else: - log.warning("hyperhealth.migrations_warning", stderr=result.stderr) - except Exception as e: - log.warning("hyperhealth.migrations_error", error=str(e)) - + + # Migrations run once in the container CMD, before uvicorn forks its + # workers. lifespan executes per worker, so running alembic here meant + # concurrent `upgrade head` calls racing each other on every boot. + log.info("hyperhealth.db_ready") await _ping_healer() yield