Skip to content

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.

  1. yamonco/fractalops-frontend:portal 변경
  2. frontend repo quality/build 검증
  3. FRACTALOPS_PORTAL_ADAPTER=cloudflare로 Astro SSR build
  4. portal/dist/server/entry.mjs, portal/dist/server/wrangler.json, portal/dist/client/_astro 검증
  5. main에서 Wrangler가 Cloudflare Worker를 배포
  6. 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"]

Run frontend checks in yamonco/fractalops-frontend, not here:

  • pnpm run lint:frontend:ci
  • pnpm run portal:check
  • pnpm run portal:build

Production-like local verification uses the Cloudflare adapter and Wrangler dry run:

Terminal window
pnpm install --frozen-lockfile
FRACTALOPS_PORTAL_ADAPTER=cloudflare pnpm --dir portal build
pnpm --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.io

The exact asset hash changes per build. portal/dist/client/_astro must exist, and Wrangler must accept the generated server config.

GitHub Actions workflow:

  • yamonco/fractalops-frontend:.github/workflows/cloudflare-portal-release.yml

이 workflow는:

  1. Cloudflare cutover contract를 build 전에 검증
  2. hosted runner에서 pnpm dependency cache를 사용
  3. Cloudflare adapter로 Astro SSR build
  4. dist/server/entry.mjs, dist/server/wrangler.json, dist/client/_astro 검증
  5. main에서는 Wrangler로 Cloudflare Worker 배포
  6. public portal.yamon.io smoke. 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.

Production Cloudflare deployment is enabled only when:

  • CLOUDFLARE_API_TOKEN, FRACTALOPS_CLOUDFLARE_API_TOKEN, or FRACTALOPS_CF_API_TOKEN exists.
  • CLOUDFLARE_ACCOUNT_ID, FRACTALOPS_CLOUDFLARE_ACCOUNT_ID, or FRACTALOPS_CF_ACCOUNT_ID exists.
  • PORTAL_API_BASE_URL, FRACTALOPS_PORTAL_API_BASE_URL, or FRACTALOPS_API_GATEWAY_URL points to the public HTTPS API origin. In the lab topology this is https://fractalops-api.yamon.io, projected from service_profiles.fractalops-api to the api edge 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_ID and PORTAL_API_CF_ACCESS_CLIENT_SECRET as a pair. Aliases are FRACTALOPS_PORTAL_API_CF_ACCESS_CLIENT_ID / FRACTALOPS_PORTAL_API_CF_ACCESS_CLIENT_SECRET and FRACTALOPS_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.

  • 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.jsonc owns the Worker name, route, compatibility flags, and asset binding.
  • portal/astro.config.mjs must keep Cloudflare adapter selection explicit via FRACTALOPS_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-Id and CF-Access-Client-Secret into 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가 내부 *.svc API 주소를 물고 배포되는 일을 막고
  • Worker 배포가 같은 Wrangler config로 local dry-run과 CI에서 재현된다.