Deploy the backend, not just the frontend
Check / guardrails (push) Successful in 1m23s
Check / bundle (push) Successful in 1m14s
Image / image (push) Successful in 2m15s
Check / check (push) Successful in 1m14s

`compose up -d` decides whether to replace a container by comparing its
configuration, and the configuration is the string `runway:latest` whether or
not the image behind that tag has moved. It never moves. So every deploy since
this script was written swapped the frontend and left the backend exactly where
it was, and the first one to matter was the one just now: the sidebar's new
editor asks `/api/calendars` for `own_color`, and the backend answering it was
built three days before that field existed.

This is the third variation on one theme and the worst of them. The first
failed loudly, the second failed quietly and exited 1; this one reports
success, and what it leaves behind is precisely the state the single-image
design exists to prevent -- a frontend calling an API the running backend does
not have. `--force-recreate` is the fix.

The check after it is the part worth keeping. Nothing above it could have
caught this, because nothing failed: podman was asked to bring a service up, it
was already up, and it said so. So the last thing the script does before
recording a deploy is compare what is actually running against what it just
pulled, and refuse to write the stamp if they disagree. A run that half-happens
now leaves no record, which is what makes the next tick try again.
This commit is contained in:
2026-08-31 15:03:40 -04:00
parent 227e21bc04
commit 50cb77556d
+22 -1
View File
@@ -121,7 +121,28 @@ mv "$staged" "$DIST"
chmod -R a+rX "$DIST"
# Then the backend, which runs the migrations as it starts.
( cd "$COMPOSE_DIR" && "$rt" compose up -d "$SERVICE" )
#
# `--force-recreate`, because the tag does not move. `compose up -d` decides
# whether to replace a container by comparing its *configuration*, and the
# configuration here is the string `runway:latest` either way -- so a service
# already running was left exactly where it was, every time, while the frontend
# beside it was replaced on every run. That is this script's third variation on
# the same theme and the worst of them: it does not fail, it half-succeeds, and
# what it leaves behind is the one state the single-image design exists to
# prevent -- a new frontend calling an API the running backend does not have.
( cd "$COMPOSE_DIR" && "$rt" compose up -d --force-recreate "$SERVICE" )
# So a half-deploy cannot be reported as a whole one. Everything above this
# either worked or the script has already exited; what is checked here is the
# thing no exit code covered -- that what is now running is what was just
# pulled.
running="$("$rt" inspect --format '{{.Image}}' "$("$rt" ps --filter "name=$SERVICE" --format '{{.Names}}' | head -1)" 2>/dev/null || true)"
running="${running#sha256:}"
if [ -n "$running" ] && [ "$running" != "$image" ]; then
echo "runway: the frontend is on ${image:0:12} and the backend is on ${running:0:12}." >&2
echo " Not recording this as deployed, so the next run tries again." >&2
exit 1
fi
# Last, so that anything above failing means the next run tries again. A hand
# rollback (see the README) leaves this pointing at the image you rolled away