fix(dev): give the dev backend image an entrypoint so it auto-migrates#469
Merged
Conversation
The dev backend image (apps/backend/Dockerfile, built by docker-compose.dev.yml and the install-uat single-role job) had CMD ["uvicorn", ...] but NO ENTRYPOINT, so the container ran uvicorn directly and skipped docker-entrypoint.sh. Its AUTO_MIGRATE auto-migration therefore never ran: alembic_version was never created, the dev healthcheck /health/ready stayed 503, the backend was permanently `unhealthy`, worker/beat (depends_on backend: service_healthy) never started, and `install.sh --no-prompt`'s `up -d` failed with "backend unhealthy". This is why the install-uat single-role job has been red since 2026-06-07, and the same class as the install.sh up-d-deadlock the L1 work surfaced. The compose comment already promised auto-migration; the image just wasn't wired for it. Add `ENTRYPOINT ["/app/docker-entrypoint.sh"]` (matching Dockerfile.prod:98) plus an explicit `COPY --chmod=0755 docker-entrypoint.sh` so the +x bit is guaranteed in the layer. The script applies `alembic upgrade head` (AUTO_MIGRATE default true) then `exec "$@"` runs the unchanged `uvicorn --reload` CMD. No tini (not in the slim dev image; uvicorn owns its own SIGTERM), USER stays root for bind-mount uid parity. Verified locally with clean volumes: before → no "alembic upgrade head" log, alembic_version absent, /health/ready 503, unhealthy; after → entrypoint runs the migration, alembic_version at HEAD (0037), 34 tables, /health/ready 200, healthy at t+5s, and uvicorn --reload still hot-reloads on a source edit. The CI install-uat single-role run is the authoritative end-to-end confirmation.
…il dev entrypoint fix) With the dev-backend entrypoint fix, install.sh now succeeds and the smoke step runs for the first time — exposing two stale bugs it always skipped past: the login path was /v1/auth/login (the auth router mounts at /auth) and the admin email used the reserved .local TLD (Pydantic EmailStr 422s). Point both the install.sh bootstrap (INSTALL_ADMIN_EMAIL) and the smoke login at admin@uat.example.com and /auth/login, matching the install-uat-l1 job.
With install.sh + smoke now passing, the backup round-trip runs for the first time and fails: docker-compose.dev.yml bind-mounts ./backups into the backend, and since the dir didn't exist the docker daemon created it root-owned, so the host-side backup.sh (non-root runner) couldn't mkdir the timestamped subdir (Permission denied). Create ./backups as the runner before the stack starts.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Root cause (finding 2)
The
install-uatsingle-role job has been red since 2026-06-07 — I traced itto the dev backend image, not backup/restore (those steps were skipped, never
reached).
apps/backend/Dockerfile(the DEV image, built bydocker-compose.dev.ymlandthe install-uat single-role job) had
CMD ["uvicorn", ...]but noENTRYPOINT, so the container ran uvicorn directly and skippeddocker-entrypoint.sh. ItsAUTO_MIGRATEauto-migration therefore never ran:alembic_versionnever created → dev healthcheck/health/readystuck at 503unhealthy→worker/beat(depends_on backend: service_healthy) never start →install.sh --no-prompt'sup -dfails with"backend unhealthy".
(Same class as the
up -ddeadlock the L1 work surfaced.) The prod image(
Dockerfile.prod:98) wiresENTRYPOINT ["/usr/bin/tini","--","/app/docker-entrypoint.sh"];the dev image simply wasn't wired for it, even though the compose comment already
promised auto-migration.
Fix (
apps/backend/Dockerfile)ENTRYPOINT ["/app/docker-entrypoint.sh"]+ an explicitCOPY --chmod=0755 docker-entrypoint.sh(guarantees the +x bit in the layer). Thescript applies
alembic upgrade headthenexec "$@"runs the unchangeduvicorn --reloadCMD. No tini (not in the slim dev image; uvicorn owns itsown SIGTERM),
USERstays root for bind-mount uid parity.Verified locally (clean volumes, before/after)
alembic upgrade headlog,alembic_versionabsent,/health/ready503,unhealthy.alembic_versionat HEAD (0037), 34tables,
/health/ready200,healthyat t+5s;uvicorn --reloadstillhot-reloads on a source edit.
Full literal
install.sh --no-promptcouldn't run locally (host ports 5432/6379held by the user's ssh tunnels), so the CI
install-uatsingle-role dispatch isthe authoritative end-to-end confirmation — I'll dispatch it on this branch.
Only
apps/backend/Dockerfile+ adocker-compose.dev.ymlcomment changed; prodimage, app code, and
install.shuntouched.