feat(deploy): version-tagged release images + same-origin frontend mode - #54
Merged
Conversation
Pushing a v* tag now publishes ghcr.io/<owner>/flagpost-{backend,
frontend}:vX.Y.Z (+ :latest) — pinned images for reproducible deploys
(release-images.yml; workflow_dispatch on a tag ref backfills an
existing tag).
The piece that makes a versioned *frontend* image meaningful: the demo
image bakes an absolute origin (NEXT_PUBLIC_API_URL) into the bundle, so
a tagged copy would be hard-wired to one operator's domain. The release
image instead bakes an **empty** origin — same-origin mode: apiFetch and
apiAssetUrl already degrade to relative paths (documented at the base
constant now), and ws.ts derives its ws(s) base from window.location at
connect time, since relative URLs in `new WebSocket()` aren't reliable
across browsers. One image then works behind any single-origin proxy
(the documented Caddy topology) with no PUBLIC_ORIGIN rebuild.
Proven end to end, not assumed: built the frontend image exactly as the
workflow does (empty build-arg) and ran it under the production compose
+ Caddy — first-run setup, login, and every API call went through
localhost:8080/api same-origin, and the scoreboard's WS room connected
through ws://localhost:8080 (the "live" badge renders only on an open
socket).
That smoke test also caught a real production-compose bug: DEMO_MODE
defaulted to an empty string (${DEMO_MODE:-}), which pydantic rejects as
a bool — a fresh `docker compose up` crash-looped the backend. Now
defaults to "false".
README documents the pull-instead-of-build path.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.
Follow-up to the v1.1.0 release: pinned, reproducible images per version tag.
What
release-images.yml— pushing av*tag builds and publishesghcr.io/<owner>/flagpost-{backend,frontend}:vX.Y.Z(+:latest).workflow_dispatchon a tag ref backfills an already-pushed tag (guarded so only tag refs can publish).NEXT_PUBLIC_API_URLinto the bundle, so a tagged copy would be hard-wired to one operator's domain. The release image bakes an empty origin instead:apiFetch/apiAssetUrlalready degrade to relative paths (contract now documented at the base constant), andws.tsderives itsws(s)://base fromwindow.locationat connect time — relative URLs innew WebSocket()aren't reliable across browsers, hence the explicit derivation. One image then works behind any single-origin proxy (the documented Caddy topology), noPUBLIC_ORIGINrebuild.Proven end to end, not assumed
Built the frontend image exactly as the workflow will (
--build-arg NEXT_PUBLIC_API_URL=) and ran it under the real production compose + Caddy:localhost:8080/api(same-origin)localhost:8080/api/*, none to a baked originws://localhost:8080— verified via the "live" badge, which renders only on an open socketBug found by the smoke test (fixed here)
docker-compose.ymlsetDEMO_MODE: ${DEMO_MODE:-}— an empty string when unset, which pydantic rejects as a bool, so a freshdocker compose upcrash-looped the backend. Now defaults to"false". This affected any new operator following the README quick start.After merge
I'll backfill v1.1.0 images via
gh workflow run release-images.yml --ref v1.1.0and verify the GHCR tags exist.Existing deployments unaffected: demo images and the build-from-source compose path are unchanged (dev/demo bake absolute origins exactly as before;
??keeps the deliberate""from falling back).🤖 Generated with Claude Code