Skip to content
Select themeSelect language

Ports and hosting domains

The Docker Compose deployment (docker-compose.yml) exposes these ports:

Service Internal port Host-mapped port Protocol
web (SvelteKit) 3000 3001 HTTP
server (Rust/axum) 8080 8080 HTTP / WebSocket
postgres 5432 not published TCP (internal only)

The web container proxies requests to server at http://server:8080 over the internal supacloud bridge network. PostgreSQL is not reachable from the host in the default Compose configuration.

Network Purpose
supacloud Internal bridge: web, server, and postgres communicate here.
supacloud-agents Isolated bridge used by agent containers spawned by server (Docker socket mounted at /var/run/docker.sock).

SupaCloud uses two distinct registrable domains (ADR 0041 / issue #410):

Domain role Env variable Example Serves
Control plane PUBLIC_URL https://app.supacloud.run API, web UI, login, SPA
Hosted App frontends SUPACLOUD_APPS_BASE_DOMAIN supacloud.net Deployed plain/React/Svelte Apps on opaque subdomains

The apps domain must not share an eTLD+1 with the control-plane host. A boot guard in secret_hydration.rs rejects any SUPACLOUD_APPS_BASE_DOMAIN value that is a sibling subdomain, superset, or subset of the control-plane origin (checked via the psl public-suffix list). See Why separate-origin App hosting for the rationale.

Each deployed hosted frontend receives a stable 80-bit hex subdomain (app_deployments.subdomain, migration 172), e.g. <opaque>.supacloud.net. The apex domain (supacloud.net) returns 404.

The #113 form-renderer App is not subject to this separation — it is served on the control-plane path (/a/{workspace_slug}/{route_path}) and has no subdomain.

interfaces/http/apps_host.rs is mounted as the outermost axum middleware. Any request whose Host header matches the apps domain (or a subdomain) is handled as App content:

  • Only GET and HEAD are accepted; other methods return 405.
  • The request never reaches the control-plane router (/api, SPA, login).
  • HEAD responses have their body stripped in this middleware (axum’s automatic stripping does not apply at middleware level).

Every file served from the apps origin carries the DEFAULT_CSP header defined in services/app_deployments/artifact.rs:

default-src 'none';
script-src 'self';
style-src 'self' 'unsafe-inline';
img-src 'self' data: blob:;
font-src 'self' data:;
connect-src 'self';
media-src 'self' blob:;
manifest-src 'self';
worker-src 'self' blob:;
base-uri 'self';
form-action 'self';
frame-ancestors 'self'

When SUPACLOUD_APPS_BASE_DOMAIN is set, the served CSP extends frame-ancestors to additionally allow the control-plane origin (csp_for_separate_origin), so the WebIDE live-preview iframe can load the App across origins. The base directive frame-ancestors 'self' is retained, so the App’s own origin plus that single control-plane origin are the only permitted framers; arbitrary sites still cannot frame the App.

X-Content-Type-Options: nosniff is set on every served file.

The hosted plain/React/Svelte frontend path is behind a flag:

Variable Default Effect when enabled
SUPACLOUD_APPS_HOSTED_FRONTEND false Serves built dist/ artifacts on the apps origin.

The flag defaults off pending the build runner image shipping with vite --base=./. The form-renderer App is unaffected by this flag.