From 739286b53b7ef904c90552a17de7db582fca80d8 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 12 Sep 2026 05:58:37 +0200 Subject: [PATCH] fix: warn when the built image is not the one a deploy will start `docker compose up` starts whatever PED_AI_IMAGE in .env names, and build-image.sh does not move that pin. A pin left from an earlier deploy therefore starts the older image while every signal reports success: the build completes, `up` says the container started, and /api/health returns {ok:true} from the wrong revision. This is not hypothetical. The pin here had been sitting on a revision from two hours before the Modify card was added, so a rebuild-and-restart rolled My Resources back 31 commits and removed the feature. The missing card was then reported as a new bug, and three deploys in this session had in fact deployed nothing. build-image.sh now compares the pin to the revision it just built and, when they differ, prints the pin, says that `up` will start it instead, and gives the command to move it. It does not correct the pin: naming a revision is also how a deliberate rollback is done, so this is said rather than silently overridden. Both deployment docs now check /api/build against `git rev-parse HEAD` after starting, because /api/health passing only proves a container is up, not that it is the one you built. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU --- docs/deployment.md | 10 ++++++++++ docs/developer-guide.md | 21 +++++++++++++++++++-- scripts/build-image.sh | 30 ++++++++++++++++++++++++++++++ 3 files changed, 59 insertions(+), 2 deletions(-) diff --git a/docs/deployment.md b/docs/deployment.md index 90bd5904..1b2c7c60 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -41,9 +41,19 @@ cd pediatric-ai-scribe-v3 cp .env.example .env # edit .env — required: APP_URL, JWT_SECRET, DATA_ENCRYPTION_KEY, DB_PASSWORD, an AI provider ./scripts/build-image.sh +sed -i "s|^PED_AI_IMAGE=.*|PED_AI_IMAGE=ped-ai-local:$(git rev-parse HEAD)|" .env docker compose up -d --no-build +curl -fsS http://127.0.0.1:3552/api/build # must equal `git rev-parse HEAD` ``` +**Building does not change what `up` starts.** `docker compose` takes the image +from `PED_AI_IMAGE` in `.env`, and `build-image.sh` deliberately does not move +that pin — naming a revision is also how a rollback is done. A pin left from an +earlier deploy therefore starts the older image, and nothing short of +`/api/build` shows it: the build succeeds, `up` reports the container started, +and `/api/health` returns `{ok:true}` from the wrong revision. `build-image.sh` +warns when the pin does not match what it just built. + The build uses Node 24 LTS and `npm ci --omit=dev` from the root lockfile. `./scripts/build-image.sh` resolves the full checkout Git commit (including worktrees/packed refs) and passes `GIT_REVISION` through Compose. It only builds; diff --git a/docs/developer-guide.md b/docs/developer-guide.md index 0045493e..6677618f 100644 --- a/docs/developer-guide.md +++ b/docs/developer-guide.md @@ -451,14 +451,31 @@ If a dynamic value enters an HTML string, escape it at the point of insertion. I ## Deployment checks -After deployment: +After deployment, check *which revision* is running, not only that something is: ```bash +curl -fsS http://127.0.0.1:3552/api/build # must equal `git rev-parse HEAD` curl -fsS http://127.0.0.1:3552/api/health docker compose ps pediatric-scribe ``` -If the browser still shows old frontend behavior, force-refresh or check the injected `BUILD_ID` asset query string. +`/api/health` passing proves a container is up, not that it is the one you +built. `docker compose up` starts whatever `PED_AI_IMAGE` in `.env` names, and +that pin does not move when you build — so a pin left from an earlier deploy +silently starts the older image, and every signal short of `/api/build` looks +fine. `scripts/build-image.sh` warns when the pin does not match the revision it +just built; the fix is to update the pin: + +```bash +sed -i "s|^PED_AI_IMAGE=.*|PED_AI_IMAGE=ped-ai-local:$(git rev-parse HEAD)|" .env +docker compose up -d --no-build +``` + +The pin is not updated for you, because naming a revision is also how a +deliberate rollback is done. + +If the browser still shows old frontend behavior after the revision checks out, +force-refresh or check the injected `BUILD_ID` asset query string. ## Documentation expectations diff --git a/scripts/build-image.sh b/scripts/build-image.sh index 29cee869..1239ba50 100755 --- a/scripts/build-image.sh +++ b/scripts/build-image.sh @@ -37,3 +37,33 @@ if [ "$GIT_REVISION" != unknown ]; then else echo 'Built ped-ai-local:latest with no recorded revision' >&2 fi + +# A deploy runs `docker compose up`, which reads PED_AI_IMAGE from .env — not +# the tag just built. A pin left behind from an earlier deploy therefore starts +# that older image, and `up` reports success either way: the build looks done, +# the health check passes, and the running app is silently an older revision. +# That has already cost a session — it rolled the app back far enough to remove +# a feature, and the missing feature was read as a new bug. +# +# Not corrected automatically: the pin is how a deploy names a revision on +# purpose, including a deliberate rollback. Said loudly instead. +if [ "$GIT_REVISION" != unknown ] && [ -f .env ]; then + PINNED=$(sed -n 's/^PED_AI_IMAGE=//p' .env | tail -1) + case "$PINNED" in + '') ;; + *"$GIT_REVISION") ;; + *) + echo >&2 + echo "WARNING: .env pins PED_AI_IMAGE=$PINNED" >&2 + echo " which is NOT the image just built." >&2 + echo " 'docker compose up -d --no-build' will start the pinned image," >&2 + echo " not this build. To deploy what was just built:" >&2 + echo >&2 + echo " sed -i 's|^PED_AI_IMAGE=.*|PED_AI_IMAGE=ped-ai-local:$GIT_REVISION|' .env" >&2 + echo >&2 + echo " Then confirm after starting it:" >&2 + echo " curl -fsS http://127.0.0.1:3552/api/build" >&2 + echo >&2 + ;; + esac +fi