Workspace subdomains
Workspace subdomains give each workspace an address such as https://ls1intum.hephaestus.build.
Authentication and the API stay on the apex, https://hephaestus.build.
The instance switch is off by default.
The web app supports workspace addresses.
Prepare DNS, TLS, and OAuth callbacks before you turn on the switch.
Configure the server
The server uses these properties:
| Property | Environment variable | Default |
|---|---|---|
hephaestus.workspace.subdomains.enabled | HEPHAESTUS_WORKSPACE_SUBDOMAINS_ENABLED | false |
hephaestus.workspace.subdomains.base-domain | HEPHAESTUS_WORKSPACE_SUBDOMAINS_BASE_DOMAIN | Empty |
The base domain must use lowercase ASCII. It must not contain a scheme, port, or path. Its maximum length is 189 characters, to leave space for a full workspace label. With the switch on, the webapp URL and auth issuer must use the HTTPS apex. The webapp URL must have no final slash.
hephaestus:
workspace:
subdomains:
enabled: true
base-domain: hephaestus.build
webapp:
url: https://hephaestus.build
auth:
issuer: https://hephaestus.build
api-base-path: /api
The server switch alone does not configure DNS, TLS, redirects, or SPA routes. Use DNS-only records and a DNS-01 wildcard certificate, as ADR 0053 requires. Cloudflare must not proxy these hosts.
Before you turn it on, configure DNS, TLS, and the apex callbacks.
To turn it off, set enabled to false in the app and proxy settings and recreate both.
Keep the apex workspace paths available.
Configure DNS and the edge
DNS and TLS can be prepared while the switch stays off.
If the zone has CAA records, allow letsencrypt.org in issue for the apex.
If it has issuewild records, they must also allow letsencrypt.org for the wildcard.
See CAA guidance.
The edge serves the same trusted SPA on every valid tenant host.
It does not check whether a workspace exists.
Cloudflare checklist for hephaestus.build
- Open the
hephaestus.buildzone and select DNS. - Create a wildcard record with Name
*, pointing to the same origin as the apex. - Set the wildcard and apex records to DNS only (grey cloud).
- Keep explicit records for reserved services, including
docsandstaging, separate from the wildcard. - Open My Profile → API Tokens → Create Token and select Edit zone DNS.
- Add Zone → Zone → Read and Zone → DNS → Edit permissions.
- Set Zone Resources to Include → Specific zone → hephaestus.build.
- Save only the raw token in
/etc/hephaestus/dns/token, with file mode0600.
Use one zone-scoped token with both permissions. Do not use the account's global API key. Cloudflare supplies DNS only, not the HTTP connection or TLS certificate. Do not configure its proxy, Origin CA, origin locks, or trusted-proxy address ranges. The server trusts only the actual Traefik ingress address.
See Cloudflare wildcard records and API token creation. The permissions follow lego's Cloudflare provider.
Helm chart and k3s
Set workspaceSubdomains.enabled: true in site.yaml.
The base domain is host. The chart passes both settings to the server and webapp.
Prepare DNS-only apex and wildcard records first.
The Gateway needs a DNS-01 certificate for both host and *.host.
The chart adds a wildcard HTTPRoute to the webapp alone. nginx rejects reserved or invalid workspace hosts and the API, auth and webhook paths on tenant hosts. Apex GET and HEAD requests to valid workspace paths return a 308 redirect that keeps the path and query. No workspace lookup controls that redirect. The server still checks every workspace permission.
For the one-host platform, prepare a cert-manager ClusterIssuer with a DNS-01 solver for your zone.
Keep its scoped credentials in a private Secret in cert-manager's namespace.
Do not put a credential value in site.yaml.
Use the cert-manager DNS-01 configuration for your DNS provider.
Make the issuer and its credential available when k3s applies the platform manifests.
On a fresh host, protected manifests can be placed in k3s's manifests directory before the installer runs.
Then add --workspace-subdomains --dns01-issuer <issuer-name> to k3s-install.sh.
The installer requests the apex and wildcard certificate from that issuer.
For a test certificate, select a test issuer. The installer's --staging flag controls only its built-in HTTP-01 issuer.
On an existing install, update site.yaml yourself and prepare the wildcard certificate before the chart upgrade.
The installer preserves an existing site.yaml.
To turn workspace hosts off, set workspaceSubdomains.enabled: false and upgrade.
Keep the DNS records and certificate while other services need them.
Compose settings
Use the same settings for the multi-file and single-host Compose stacks. The proxy chooses one ACME challenge through these optional variables:
| Variable | Default | With subdomains |
|---|---|---|
ACME_CHALLENGE | httpchallenge.entrypoint | dnschallenge.provider |
ACME_CHALLENGE_VALUE | http | Your lego DNS provider, such as cloudflare |
TRAEFIK_DNS_CREDENTIALS_DIRECTORY | /etc/hephaestus/dns | Absolute path to the proxy-only credential directory |
TRAEFIK_DNS_CREDENTIAL_VARIABLE | CF_DNS_API_TOKEN_FILE | Your provider's credential variable with the _FILE suffix |
LEGO_EXPERIMENTAL_DNS_TCP_ONLY | false | Set true only when DNS queries need TCP transport |
For Cloudflare, create the credential directory with mode 0700.
Put only the raw zone-scoped token in its token file, with mode 0600.
Keep the directory outside release trees.
The proxy mounts it read-only at /run/secrets/dns.
lego reads the token through CF_DNS_API_TOKEN_FILE=/run/secrets/dns/token.
Compose output and container environment values contain paths, not the token.
Never add the token to an environment file, diagnostics, or a release artifact.
HTTP-01 deployments need no token.
For another provider, use its name and required variables from lego's provider list.
Choose a provider that supports certificates for both a wildcard and its apex.
Set TRAEFIK_DNS_CREDENTIAL_VARIABLE to its _FILE credential name.
For a provider with multiple credentials, store the other files in the same directory.
Add its other _FILE variables to the proxy environment through a Compose override.
Each value must point to its file under /run/secrets/dns.
See lego credential files.
Do not disable DNS propagation checks.
Allow the proxy to reach the provider API and public DNS servers.
If authoritative DNS queries time out over UDP while TCP works, set
LEGO_EXPERIMENTAL_DNS_TCP_ONLY=true in the proxy settings and recreate the proxy.
This uses lego's TCP transport
for DNS queries, including propagation checks.
It leaves the existing propagation-check settings unchanged.
The default is false.
Set it back to false and recreate the proxy to restore the default transport.
Set these non-secret values alongside APP_HOSTNAME:
APP_HOSTNAME=hephaestus.build
HEPHAESTUS_WORKSPACE_SUBDOMAINS_ENABLED=false
HEPHAESTUS_WORKSPACE_SUBDOMAINS_BASE_DOMAIN=hephaestus.build
ACME_CHALLENGE=dnschallenge.provider
ACME_CHALLENGE_VALUE=cloudflare
TRAEFIK_DNS_CREDENTIALS_DIRECTORY=/etc/hephaestus/dns
TRAEFIK_DNS_CREDENTIAL_VARIABLE=CF_DNS_API_TOKEN_FILE
In pull-based production deployment, set the switch and base domain in both app.env and proxy.env.
Set the ACME variables and credential-directory path in proxy.env only.
The promotion workflow selects a verified release.
It does not upload or replace host secrets.
See pull-based deployment.
For the single-host installer, set these values in its one .env file.
With DNS-01 and a nonempty base domain, Traefik requests the apex and *.<base> certificate
through its default TLS store, even while workspace subdomains stay off.
Recreate the proxy and verify both certificate names before enabling workspace routing.
The base domain must equal APP_HOSTNAME during preparation as well as after enablement.
ACME state stays in the existing persistent volume.
Without these preparation settings, the default remains HTTP-01. Keep a working DNS-01 configuration during switch reversal if other services still use the wildcard certificate. See Traefik ACME.
Check and turn off
- Upgrade the app and proxy to this release with the switch off.
- Prepare the DNS-only wildcard record. Apply the proxy settings above with the switch off and verify the apex/wildcard certificate.
- Register the exact apex OAuth callbacks and verify apex sign-in.
- Set the switch and base domain in both app and proxy settings.
- Recreate the app and proxy with Compose's
--waitoption. - Verify workspace navigation, public activity, sign-in, and an authenticated change on a workspace host.
The webapp container receives the same switch and base domain as the server.
APPLICATION_CLIENT_URL is the apex app origin.
When the switch is on, the web app container refuses to start if this value is missing.
APPLICATION_SERVER_URL is the absolute apex API URL, including /api.
The reference Compose configuration supplies these values. For another deployment, supply all four values to the webapp container. Keep the server, webapp, and proxy settings consistent.
Recreate the app and proxy with Compose's --wait option.
Invalid edge settings stop the proxy with a clear startup error instead of serving a partial configuration.
Check the container logs if Compose reports a startup failure.
Check that a valid workspace host has a trusted certificate and serves the web app.
Check that an apex /w/<slug> link redirects to the same path and query on its workspace host.
Apex sign-in, consent, auth, legal, unsubscribe, instance administration, and settings stay on the apex.
Tenant hosts must not serve API or OAuth paths.
Do not add tenant hosts or broad wildcard expressions to APP_HOST_MATCH.
That variable is only for the apex and explicit instance aliases.
To turn it off, set the switch to false in app and proxy settings and recreate both with --wait.
Apex workspace links then serve the web app without a tenant redirect.
No DNS record, credential, or ACME-state deletion is needed.
Check the same-site precondition
Use a DNS base domain, not an IP address.
The base domain must not be a public suffix.
Check both the ICANN and PRIVATE sections of the current list.
Include wildcard and exception entries in the check.
Check that tenant hosts have the same registrable domain as the apex.
A public suffix would put the apex and tenant hosts on different sites.
The unchanged SameSite cookies would then prevent the tenant SPA from using the apex session.
Register exact OAuth callbacks
Every login provider uses one callback on the configured auth issuer's origin.
The server adds api-base-path and /login/oauth2/code/<registrationId>.
Neither Host nor X-Forwarded-Host can change that callback.
Instance administration shows the same callback that authentication uses.
- Open Login providers in instance administration.
- Copy each provider's callback URI.
- If the registered address differs, register the exact URI shown.
- In the GitHub login app settings, set Callback URL to
https://hephaestus.build/api/login/oauth2/code/github. - Turn off Enable wildcard matching in the callback URL and remove tenant-host callbacks.
- In the GitLab OAuth application, set Redirect URI to
https://hephaestus.build/api/login/oauth2/code/gitlab. - In the Outline OAuth application, set Redirect URI to
https://hephaestus.build/api/login/oauth2/code/outline.
Self-hosters replace hephaestus.build with their apex and use the registration ID shown in instance administration.
Do not add a final slash or wildcard.
Dynamic providers can have a different registration ID.
GitHub, GitLab, Slack sign-in, and Outline use this policy.
The separate Slack workspace connection uses its configured hephaestus.integration.slack.redirect-uri.
Set that URI to the exact apex /api/oauth/callback/slack address.
GitHub App installations and GitLab token connections do not use a request-derived login callback.
If the registered callback already matches the configured issuer origin and API prefix, no callback update is needed. This includes existing callbacks registered at the apex. See GitLab OAuth applications and Outline's OAuth provider. See GitHub's callback guidance. Exact callback comparison follows OAuth Security Best Current Practice.
Browser access and CSRF
The CORS policy permits exact configured origins and installed extension origins as before.
With subdomains on, it also permits an exact HTTPS origin with one valid, non-reserved workspace label.
It checks the entire origin, not a suffix substring.
The check does not look up a workspace.
An unknown valid label gets the same CORS response as a current workspace label.
The docs host is never a tenant origin, even if it appears in the configured origins.
Serve only the trusted Hephaestus SPA on tenant hosts. Never host uploaded HTML, third-party applications, or workspace-controlled scripts there. Reserve each label that another service uses before you enable subdomains. A trusted origin can read the browser session through the apex API.
The policy does not grant workspace membership. Every API request still passes the existing authorization checks.
The tenant SPA cannot read the apex __Host-XSRF-TOKEN cookie.
It must fetch GET https://<apex>/api/auth/csrf with credentials.
The response contains the raw token and its headerName.
Send that header on unsafe requests to the apex, with credentials.
At startup, fetch the token before any other credentialed request. Sign-in and sign-out reload the page. The new page fetches the current token before it sends credentialed requests. Wait for the response before starting other requests.
A CSRF 403 has problem type urn:hephaestus:csrf.
On that response, refetch the token once and retry the rejected request once.
Do not retry another 403 or an ambiguous network failure.
OAuth sign-in and browser sign-out clear the previous CSRF cookie through Spring Security.
The endpoint is available before sign-in and sends Cache-Control: no-store.
Do not cache or compress this response at the edge.
It must not appear in logs.
The auth cookies keep the __Host- prefix, Secure, Path=/, and no Domain.
Return paths remain relative-only.
Workspace detail and list responses expose the canonical workspaceAddress for the SPA.
Browser state and navigation
Each origin keeps its own View as user session.
The theme is the exception: the hephaestus-theme cookie on the base domain holds it, so it stays the same on every workspace host.
The cookie can hold only light, dark or system, so another host under the base domain can change only the theme.
No browser storage or session token is copied between hosts.
The workspace-specific View as user entry opens on the workspace host before it stores the view.
Instance administration stays on the apex.
Workspace administration uses /admin on the workspace host.
This is the only shared prefix: internal /admin links target instance administration on the apex.
Unambiguous apex paths, including /workspaces/new, leave the workspace host.
Links to instance administration use absolute apex URLs.
Sign-in uses the apex /login page.
The return path is relative, such as /w/<slug>/activity?range=1y.
After sign-in, a document request reaches the edge's workspace redirect.
The server does not accept an absolute return path.
Slack connection callbacks derive their return workspace from the signed workspace ID, not browser storage.
The extension reads /.well-known/hephaestus.json on a pasted workspace host.
This public, non-cacheable document names the configured apex and base domain.
The extension asks for access to the apex before it connects.
It does not guess a parent domain or send credentials during discovery.
Workspace names and upgrade
A slug is a lowercase ASCII DNS label with 3 to 51 characters.
It cannot start or end with a hyphen.
Consecutive hyphens are prohibited.
This also excludes xn-- names.
This follows ADR 0053.
Infrastructure and application names are reserved.
These include www, api, docs, admin, auth, login, mail, status, staging, preview, and pr<digits>.
The policy also reserves RFC 2142 mailbox names and the application's route names.
The migration preserves valid slugs.
It replaces an invalid or reserved slug with workspace-<id>.
If that address was already used, it adds a numeric suffix.
The migration records the old address in slug history.
Old paths that match the previous slug format redirect directly to the current slug, with authorization unchanged.
Rename redirects do not expire or discard old entries. A used slug cannot name another workspace, even after workspace deletion. Database reservations enforce this across concurrent writes.
Make and verify a database backup before the upgrade. Check workspace addresses after the upgrade. The migration waits at most one second for each required table lock. If a long transaction prevents it from acquiring a lock, startup fails without a partial slug migration. Retry startup after that transaction finishes.
The database cannot recover history that an earlier version deleted. Reservations cover all current slugs and all retained history at upgrade time. Restore the verified pre-upgrade backup to reverse the migration.