Portal Delivery Flow
Portal Delivery Flow
Section titled “Portal Delivery Flow”fractalops-portal source/build ownership lives in private repo
yamonco/fractalops-frontend. The production portal is an Astro SSR app, not a
static site. Docs for this flow live in Starlight only.
Canonical Flow
Section titled “Canonical Flow”yamonco/fractalops-frontend:portal변경- frontend repo quality/build 검증
FRACTALOPS_PORTAL_ADAPTER=cloudflare로 Astro SSR buildportal/dist/server/entry.mjs,portal/dist/server/wrangler.json,portal/dist/client/_astro검증main에서 Wrangler가 Cloudflare Worker를 배포- public
https://portal.yamon.io/smoke 또는 Cloudflare edge reachability 확인
The frontend repo owns the release artifact. The control-plane repo does not carry a Portal image pin for this route. Dokploy is not the canonical production Portal release path. There is no Vercel, GHCR image, or frontend container release path for the Portal. If the Portal needs SSR, deploy the Cloudflare Worker; do not wrap it in another release plane.
flowchart LR
subgraph fe["yamonco/fractalops-frontend (source + build owner)"]
change["portal change"] --> verify["quality / build verify"]
verify --> build["Astro build with Cloudflare adapter"]
build --> assets["verify server entry + wrangler.json + dist/client/_astro"]
end
subgraph delivery["GitHub Actions + Cloudflare Workers"]
guard["cutover contract guard"]
deploy["wrangler deploy"]
smoke["public portal smoke / edge challenge notice"]
end
assets --> guard
guard --> deploy
deploy --> smoke
smoke --> portal["portal.yamon.io"]
Local Commands
Section titled “Local Commands”Run frontend checks in yamonco/fractalops-frontend, not here:
pnpm run lint:frontend:cipnpm run portal:checkpnpm run portal:build
Production-like local verification uses the Cloudflare adapter and Wrangler dry run:
pnpm install --frozen-lockfileFRACTALOPS_PORTAL_ADAPTER=cloudflare pnpm --dir portal buildpnpm --dir portal exec wrangler deploy \ --config dist/server/wrangler.json \ --dry-run \ --keep-vars \ --var PORTAL_PUBLIC_URL:https://portal.yamon.io \ --var FRACTALOPS_PORTAL_PUBLIC_URL:https://portal.yamon.ioThe exact asset hash changes per build. portal/dist/client/_astro must exist,
and Wrangler must accept the generated server config.
CI Flow
Section titled “CI Flow”GitHub Actions workflow:
yamonco/fractalops-frontend:.github/workflows/cloudflare-portal-release.yml
이 workflow는:
- Cloudflare cutover contract를 build 전에 검증
- hosted runner에서 pnpm dependency cache를 사용
- Cloudflare adapter로 Astro SSR build
dist/server/entry.mjs,dist/server/wrangler.json,dist/client/_astro검증main에서는 Wrangler로 Cloudflare Worker 배포- public
portal.yamon.iosmoke. GitHub-hosted runner가 Cloudflare bot challenge를 받으면 deploy 성공 후 notice로 기록하고, 브라우저/live verification을 acceptance gate로 둔다.
SSH remote build and Dokploy rebuild are not supported production release paths for the Portal. They are too brittle for the frontend: connection failure blocks deploy before build evidence exists, and remote rebuild hides the exact release artifact.
On main push, an incomplete cutover contract skips deployment before dependency
install so the branch does not go red for an intentionally unready release. On
manual workflow_dispatch, the same condition fails fast.
Cutover Contract
Section titled “Cutover Contract”Production Cloudflare deployment is enabled only when:
CLOUDFLARE_API_TOKEN,FRACTALOPS_CLOUDFLARE_API_TOKEN, orFRACTALOPS_CF_API_TOKENexists.CLOUDFLARE_ACCOUNT_ID,FRACTALOPS_CLOUDFLARE_ACCOUNT_ID, orFRACTALOPS_CF_ACCOUNT_IDexists.PORTAL_API_BASE_URL,FRACTALOPS_PORTAL_API_BASE_URL, orFRACTALOPS_API_GATEWAY_URLpoints to the public HTTPS API origin. In the lab topology this ishttps://fractalops-api.yamon.io, projected fromservice_profiles.fractalops-apito theapiedge bridge port.- The API origin is not
portal.yamon.io, not Kubernetes*.svc/cluster.local, not loopback, and not RFC1918 private address space. - If the API origin is Cloudflare Access protected, the Worker receives
PORTAL_API_CF_ACCESS_CLIENT_IDandPORTAL_API_CF_ACCESS_CLIENT_SECRETas a pair. Aliases areFRACTALOPS_PORTAL_API_CF_ACCESS_CLIENT_ID/FRACTALOPS_PORTAL_API_CF_ACCESS_CLIENT_SECRETandFRACTALOPS_GATEWAY_CF_ACCESS_CLIENT_ID/FRACTALOPS_GATEWAY_CF_ACCESS_CLIENT_SECRET.
This guard exists because the previous in-cluster Portal could call
fractalops-api.fractalops.svc.cluster.local. A Cloudflare Worker cannot. It
also prevents accidental self-recursion where the Worker calls
https://portal.yamon.io as its API base.
Design Rules
Section titled “Design Rules”- Portal release artifact is the Cloudflare Worker build output, not a copied worktree and not a remote Dokploy rebuild.
- The Portal frontend has one production release surface: Wrangler publishes the Worker and its static assets. Vercel shims, Dokploy static shims, and frontend OCI image releases are legacy paths and must stay deleted.
portal/wrangler.jsoncowns the Worker name, route, compatibility flags, and asset binding.portal/astro.config.mjsmust keep Cloudflare adapter selection explicit viaFRACTALOPS_PORTAL_ADAPTER=cloudflare.- Portal identity/session state is owned by the API and forwarded edge identity.
Astro’s default Cloudflare KV session auto-provisioning must stay disabled
unless a Portal feature explicitly adopts
Astro.session. - API Cloudflare Access service tokens are server-only Worker secrets. The
Portal may inject
CF-Access-Client-IdandCF-Access-Client-Secretinto upstream API requests, but it must never forward browser-supplied service-token headers. - Push-triggered release must skip before dependency install when the production API origin is missing or private.
- Manual release must fail before dependency install when the production API origin is missing or private.
- Smoke must prove the public Portal route returns HTTP 200 when Cloudflare
allows the runner. If the runner receives
Just a moment..., the workflow records a notice because the Worker deploy already succeeded and live browser verification is the acceptance gate. - Docs for Portal delivery live in Starlight, under
docs/src/content/docs. - Repo README can point at the docs site, but must not become the delivery SSOT.
이 구조를 쓰면:
- 프론트 변경이 Kubernetes/Dokploy worker 상태에 묶이지 않고
- build 실패와 cutover 실패가 빠르게 분리되며
portal.yamon.io가 내부*.svcAPI 주소를 물고 배포되는 일을 막고- Worker 배포가 같은 Wrangler config로 local dry-run과 CI에서 재현된다.