Skip to content

fix(dev): give the dev backend image an entrypoint so it auto-migrates#469

Merged
haksungjang merged 3 commits into
mainfrom
fix/dev-backend-entrypoint
Jul 5, 2026
Merged

fix(dev): give the dev backend image an entrypoint so it auto-migrates#469
haksungjang merged 3 commits into
mainfrom
fix/dev-backend-entrypoint

Conversation

@haksungjang

Copy link
Copy Markdown
Contributor

Root cause (finding 2)

The install-uat single-role job has been red since 2026-06-07 — I traced it
to the dev backend image, not backup/restore (those steps were skipped, never
reached).

apps/backend/Dockerfile (the DEV image, 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 never created → dev healthcheck /health/ready stuck at 503
  • backend permanently unhealthyworker/beat (depends_on backend: service_healthy) never start → install.sh --no-prompt's up -d fails with
    "backend unhealthy".

(Same class as the up -d deadlock the L1 work surfaced.) The prod image
(Dockerfile.prod:98) wires ENTRYPOINT ["/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 explicit
COPY --chmod=0755 docker-entrypoint.sh (guarantees the +x bit in the layer). The
script applies alembic upgrade head 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 (clean volumes, before/after)

  • 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; uvicorn --reload still
    hot-reloads on a source edit.

Full literal install.sh --no-prompt couldn't run locally (host ports 5432/6379
held by the user's ssh tunnels), so the CI install-uat single-role dispatch is
the authoritative end-to-end confirmation
— I'll dispatch it on this branch.

Only apps/backend/Dockerfile + a docker-compose.dev.yml comment changed; prod
image, app code, and install.sh untouched.

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.
@haksungjang haksungjang merged commit fa3da1e into main Jul 5, 2026
23 checks passed
@haksungjang haksungjang deleted the fix/dev-backend-entrypoint branch July 5, 2026 09:53
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