Custom Domain
Self-hosting

Configuration

Environment variables for the control-plane, edge, and console, plus production guardrails.

Naming: the rebrand fallback

Control-plane variables are read CUSTOMDOMAIN_* first, then JUSTEASY_* as a fallback, so deployments from before the rename keep working. Either prefix works; CUSTOMDOMAIN_* wins if both are set. The tables below list the canonical JUSTEASY_* name.

Control-plane

VarPurposeDefault
JUSTEASY_ADDRHTTP listen address:8080
JUSTEASY_ENVproduction enables guardrailsdevelopment
JUSTEASY_DATABASE_URLPostgres DSN (in-memory store if unset)unset (required in prod)
JUSTEASY_TOKEN_TTLwidget-JWT lifetime60m
JUSTEASY_MANAGEMENT_KEYgates provisioning + cross-tenant readsunset (required in prod)
JUSTEASY_PROVISION_SECRETgates POST /v1/tenants:provisionunset
JUSTEASY_EDGE_CNAME_TARGEThost connected domains point atedge.customdomain.ai
EDGE_ASK_SECRETshared secret for /internal/ask (X-Edge-Auth)unset (ask disabled)
SELL_PURCHASE_ENABLED1/true allows registrar purchases (fail-closed)off
SECURE_ENABLED1/true enables the Secure (/ssl certificate) surface; otherwise every /ssl route reports not-configuredoff
POWER_ENABLED1/true enables the Power (/power edge-origin) surface; otherwise every /power route reports not-configuredoff
MONITOR_ALERTS_ENABLED1/true lets the hourly drift sweeper deliver domain.record_missing/_restored webhooks (it still runs in shadow mode when off)off
OAUTH_STATE_KEYHMAC key persisting OAuth state across restartsboot-time key
OAUTH_ALLOWED_RETURN_ORIGINScomma-separated postMessage allowlistunset
OAUTH_<PROVIDER>_CLIENT_ID / _CLIENT_SECRETper-provider OAuth app (Rail C)unset
DC_SIGNING_KEY_FILE / DC_SIGNING_KEY_SELECTORRS256 key + DNS selector for Domain Connect apply URLsunset
STRIPE_SECRET_KEYenables billing checkout/portalunset (catalog still served)
STRIPE_PRICE_STARTUP / STRIPE_PRICE_GROWTH / STRIPE_PRICE_GROWTH_METEREDStripe price idsunset
NAMECOM_USERNAME / NAMECOM_TOKENregistrar (Sell) credentialsunset
SCIM_BEARER_TOKEN / SCIM_BASE_URLenterprise SCIM 2.0 provisioningunset

The propagation poller runs automatically (1-minute interval) — there is no separate enable flag.

Edge

VarPurposeDefault
--addr (flag)HTTPS listen address:8443
EDGE_ASK_URLcontrol-plane origin for the fail-closed gate (POST {EDGE_ASK_URL}/internal/ask); set ⇒ productionunset (dev/mock gate)
EDGE_ASK_SECRETshared secret sent as X-Edge-Authunset
EDGE_ACME_PROD1 uses the Let's Encrypt production directory (else staging)unset
EDGE_ACME_EMAILACME account contact""
EDGE_CERT_CACHEautocert cert/account cache dir/data/certs
EDGE_HTTP_ADDR:80 listener for ACME HTTP-01:80

In dev (neither EDGE_ASK_URL nor EDGE_ACME_PROD set) the edge issues self-signed certs and uses a mock gate, so go test and local runs need no network. It refuses to run EDGE_ACME_PROD with an empty EDGE_ASK_URL (an ungated issuer).

Console (apps/app)

VarPurpose
CP_BASE_URLcontrol-plane base the server proxy calls
WEB_SECRET_KEYseals each tenant's control-plane API key at rest (AES-256-GCM)
BETTER_AUTH_SECRETsession-signing secret — required in production (the app fails closed if unset)
BETTER_AUTH_URL / NEXT_PUBLIC_APP_URLcanonical app URL for auth callbacks
DATABASE_URLPostgres for the console's own tables
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECREToptional social sign-in
JUSTEASY_PROVISION_SECRETthe provision secret the console sends to tenants:provision (CD_PROVISION_SECRET also accepted)

Production guardrails

Setting JUSTEASY_ENV=production makes the control-plane refuse to start on unsafe configuration:

  • JUSTEASY_DATABASE_URL unset (or unreachable) → refuse to start.
  • JUSTEASY_MANAGEMENT_KEY unset → refuse to start (provisioning would be open).
  • The edge refuses EDGE_ACME_PROD without EDGE_ASK_URL.

Health

GET /healthz is the liveness + database-readiness probe (no tenant data, no secrets). The edge gate POST /internal/ask is the only control-plane call the edge makes and must be network-isolated to the edge.

On this page