Custom Domain
Self-hosting

Overview

Components, local development, deployment topology, and an honest capabilities list.

Components

  • control-plane (Go + Postgres) — the /v1 REST API, the connect rails, the propagation poller, and /internal/ask.
  • edge (Go, on-demand TLS via ACME autocert + reverse proxy) — terminates TLS and proxies live custom domains, gated by ask.
  • console (apps/app, Next.js) — the developer dashboard and this docs site; it brokers control-plane calls with a sealed per-tenant key.
  • mcp (Go) — an optional Model Context Protocol server that exposes domain search/connect/purchase to AI agents over JSON-RPC; it wraps the same control-plane /v1 API.
  • Postgres — the system of record.

The full compose files, Caddyfile, and ops scripts live in infra/; a component-level design overview is in docs/architecture/ARCHITECTURE.md.

Local development

make up     # docker compose: Postgres + control-plane + edge
make build  # go build across the services
make test   # go test across the services

The control-plane listens on :8080; the edge on :8443 (TLS) and :80 (ACME HTTP-01). With no JUSTEASY_DATABASE_URL set the control-plane uses an in-memory store, and the edge runs in dev mode (self-signed certs + a mock gate) until EDGE_ASK_URL / EDGE_ACME_PROD are set.

Deployment topology

  1. Run Postgres and set JUSTEASY_DATABASE_URL (required in production).
  2. Run the control-plane; expose only /v1/* publicly. Keep /internal/ask reachable only from the edge, protected by a shared EDGE_ASK_SECRET.
  3. Run the edge with a public IP; point customer domains at it. Set EDGE_ASK_URL to the control-plane origin and EDGE_ACME_PROD=1 (with EDGE_ACME_EMAIL) for real Let's Encrypt certs.
  4. Set JUSTEASY_MANAGEMENT_KEY and JUSTEASY_PROVISION_SECRET — the control-plane refuses to start in production with provisioning left open.

Full environment-variable tables are on the configuration page.

Capabilities & limitations (honest)

What is fully automated today, and what still needs a human or a business step:

AreaStatus
DNS detection + record synthesis✅ automated (domains:check, connections)
Manual rail (copy records, poll to live)✅ automated end-to-end
API-token rail (Rail B)✅ automated; the customer must mint a scoped provider token
Machine-credential providers (Route 53 / GCP / Azure)✅ adapters built; the customer creates the cloud identity
Domain Connect (Rail A)✅ code-complete; needs DC_SIGNING_KEY_FILE configured for signed templates
OAuth-into-provider (Rail C)⚠️ code-complete, but you must register an OAuth app per provider and pass its approval process (OAUTH_<PROVIDER>_CLIENT_ID/_SECRET)
On-demand TLS + reverse proxy✅ code-complete at the edge; certs are Let's Encrypt staging (not browser-trusted) until EDGE_ACME_PROD=1
Secure — /ssl certificate lifecycle⚠️ built but flag-gated; every /ssl route reports not-configured unless SECURE_ENABLED=1
Power — /power edge origin lifecycle⚠️ built but flag-gated; off unless POWER_ENABLED=1
Monitor — on-demand monitor:check✅ automated; the hourly drift sweep (and its domain.record_missing/restored alerts) is off unless MONITOR_ALERTS_ENABLED=1
MCP agent server✅ built; runs as a separate services/mcp process, off by default
Domain purchase (Sell)⚠️ fail-closed; disabled unless SELL_PURCHASE_ENABLED, and needs registrar credentials
Billing checkout/portal⚠️ served only when STRIPE_SECRET_KEY and the STRIPE_PRICE_* ids are wired; the plan catalog is always public
Connection delete / archive❌ no API endpoint yet (see Offboarding)
Provider-eligibility gates⚠️ some providers (GoDaddy tier, Namecheap IP allowlist) gate who can get API access — not removable in code

The takeaway: the code paths are complete, but "frictionless as a service" for a given provider can still require an OAuth-app approval or a provider account tier that only your company (or the customer) can obtain.

On this page