Skip to content
Select themeSelect language

Separate-origin App hosting

Hosted App frontends (React/Svelte/plain-with-source) must be served from a different registrable domain (eTLD+1) than the control plane. If Apps were served on a subdomain of the control-plane host (supacloud.run), they would be same-site with the API and a deployed App’s JavaScript could ride the operator’s session cookie against /api. Serving from supacloud.net makes every request cross-site, so SameSite=Lax withholds the session cookie, cookie-tossing is impossible, and the CORS allowlist naturally excludes the apps origin. This is the same isolation model GitHub uses with githubusercontent.com.

The feature is also gated by SUPACLOUD_APPS_HOSTED_FRONTEND (default false). The flag must not be turned on without a separate apps domain configured — the server refuses to boot in that combination.

  • A registrable domain (eTLD+1) that is distinct from your control-plane domain. For SupaCloud SaaS this is supacloud.net; self-hosted operators should provision their own equivalent (e.g. myapp-content.example).
  • A wildcard TLS certificate for *.yourapps.domain and DNS routing for all subdomains to the SupaCloud server. The bw-infra reference deployment uses a Traefik wildcard cert via DNS-01.
  • The runner image must have been rebuilt with vite --base=./ so artifacts host at any subdomain root. Until that image is rolled out, keep the flag off.

Each hosted-frontend deployment is assigned a stable, opaque 80-bit hex subdomain (stored in app_deployments.subdomain, migration 172). The subdomain is assigned once and re-used across redeploys so the URL is stable.

When SUPACLOUD_APPS_BASE_DOMAIN is set, a host-dispatch middleware (interfaces/http/apps_host.rs) is mounted as the outermost layer of the server. Any request whose Host header matches the apps domain or one of its subdomains is served as App content and never reaches /api, the SPA, or the login page. Only GET and HEAD are accepted on the apps origin; all other methods return 405. The path-based route (/a/{workspace}/{route}) refuses hosted frontends when an apps domain is configured, so each hosted App is reachable exclusively through its opaque subdomain.

The boot guard in secret_hydration.rs rejects an apps_base_domain value that shares an eTLD+1 with the control plane (checked in both directions via the psl public-suffix list), so a misconfigured domain fails loudly at startup rather than silently collapsing the isolation boundary.

  1. Set the apps content domain.

    Add SUPACLOUD_APPS_BASE_DOMAIN to your environment, pointing at your separate registrable domain:

    SUPACLOUD_APPS_BASE_DOMAIN=supacloud.net

    The value must be a bare domain — no scheme, port, path, or spaces. supacloud.net is correct; https://supacloud.net is rejected at boot.

  2. Enable hosted-frontend serving.

    SUPACLOUD_APPS_HOSTED_FRONTEND=true

    The server refuses to boot with this flag set but no apps domain configured.

  3. Route wildcard DNS to your server.

    Every *.yourapps.domain request must reach SupaCloud. In the reference bw-infra deployment, Traefik handles a wildcard *.supacloud.net certificate (InternetBS DNS-01) and forwards all subdomains to the server.

  4. Restart the server.

    The boot guard validates both variables. On a successful start the /config API endpoint will include "apps_base_domain": "yourapps.domain".

A hosted App’s backend calls (e.g. a workflow trigger submitted via a form) should not embed a user session. Instead, mint a per-deployment App API token (prefix scwa_, stored as a hash in app_deployments.api_token_hash, migration 173):

POST /api/apps/{id}/deployment/token
Authorization: Bearer <owner-session>

The response returns the raw token value once. Store it server-side in your App’s backend — never embed it in a public frontend bundle. The token authenticates as a workspace-scoped service identity and is CSRF-exempt (a non-cookie credential; CORS preflight blocks cross-site attachment of a custom Authorization header). Revoke it when no longer needed:

DELETE /api/apps/{id}/deployment/token
Authorization: Bearer <owner-session>

The token is also automatically cleared on undeploy so a redeploy cycle cannot silently re-arm a stale credential.

After restarting, deploy a React or Svelte App from the SupaCloud UI. The deployment detail page will show the assigned subdomain URL (https://<opaque>.yourapps.domain). Opening that URL should serve the built index.html with:

  • A Content-Security-Policy header (default-deny, frame-ancestors includes the control-plane origin so the live preview iframe works cross-origin).
  • X-Content-Type-Options: nosniff.
  • <base href="/"> injected into index.html.

Requests to the control-plane origin (/api) from the apps origin are blocked by SameSite=Lax on the session cookie and excluded by the CORS allowlist.