Subdomains
How `{tenant}-{deployment}.vendo.run` resolves, plus custom domain support.
Every Vendo deployment gets a public URL on *.vendo.run. The shape and lifecycle of that URL matters more than it looks — it's how routing, TLS, and per-deployment isolation all hang together.
URL shape
New deployments use a single-level subdomain — both slugs collapse into one DNS label:
https://{tenant_slug}-{deployment_slug}.vendo.runtenant_slug— chosen once per tenant (workspace). Stable. May contain hyphens (acme-corp).deployment_slug— chosen at deploy time, defaults to the tool slug stripped of any non-alphanumeric characters (e.g.twenty,hermes). Editable later. Deployment slugs are[a-z0-9]+only; no hyphens, so the single-hyphen delimiter between the two slugs is unambiguous.
Older deployments still serve from the legacy two-level form ({deployment_slug}.{tenant_slug}.vendo.run) via per-hostname Cloudflare Worker Custom Domains. Both shapes route through the same app-proxy worker; the rename helper preserves whichever format a deployment already lives on.
Both slugs are validated against a reserved list (you can't pick api, proxy, vendo, www, etc.) and against the pattern *-proxy (which is reserved for Vendo's provider proxies). Combined {tenant}-{deployment} length is capped at 63 characters (the DNS label limit).
What happens on a request
Browser
│ GET https://acme-crm.vendo.run/contacts
▼
Cloudflare DNS ─► *.vendo.run wildcard
│
▼
App-proxy Worker
│ reads KV: deploy:acme-crm
│ → { target, vendoAuth, tenantId, status }
│
▼
Your backend (Railway / Workers)The app-proxy is a single Cloudflare Worker (vendo-app-proxy) that handles every *.vendo.run deployment URL via the wildcard Worker Route *.vendo.run/*. It looks up routing in KV, enforces auth if you opted in, and reverse-proxies to your real backend.
If the deployment is suspended, resuming, or otherwise not running, the proxy serves a status page instead of forwarding. This is why your URL never returns a confusing 404 mid-suspension.
The app-proxy also strips Cloudflare's edge headers (CF-Connecting-IP, CF-IPCountry, CF-Ray, CF-Visitor) and the Host header before forwarding. See Runtime targets for what that means for client-IP visibility.
Renaming a deployment
Tenants can change the deployment_slug after deploy. When they do, Vendo atomically:
- Updates
deployments.deployment_slugin Postgres. - Swaps the
deploy:{new_subdomain}KV entry (so the new URL routes immediately). - Deletes the
deploy:{old_subdomain}entry — old URL 404s instantly. No transparent redirect. - Rewrites
${subdomain}and${domain}env vars in your container. - Triggers a service restart so your app picks up the new env.
If your tool emits absolute URLs (in emails, webhooks, OAuth callbacks), it must read them from env at runtime — not hardcode them. Renaming will move the URL out from under you.
TLS
You don't manage certs. Two regimes coexist:
- Single-level URLs (
acme-crm.vendo.run) — covered by free Universal SSL on*.vendo.run. Default for new deployments. No per-hostname certificate provisioning step. Lifts the per-zone 100-Custom-Domain cap permanently. - Two-level URLs (
crm.acme.vendo.run, legacy) — Cloudflare provisions an Advanced certificate per hostname via a Worker Custom Domain. Universal SSL doesn't cover two-level subdomains. Provisioning took 1–10 minutes during the first deploy. Each Custom Domain consumes 1/100 of the zone's hard cap.
Both serve the same backend; the app-proxy handles them identically.
Custom domains
For production use, tenants can point their own domain (e.g. crm.acme.com) at a deployment. The flow:
- Tenant adds the domain in the dashboard.
- Vendo creates a Cloudflare Worker Custom Domain for that hostname.
- Tenant adds a
CNAMErecord at their DNS provider pointing to the deployment URL. - Cloudflare auto-provisions TLS once DNS resolves.
Once live, both the *.vendo.run URL and the custom domain route to the same backend through the same proxy.
What your tool sees
In your container's env:
${domain}— the public hostname (with.vendo.runor a custom domain).${subdomain}— the left-most label.
Always read these at runtime. Don't bake them into a build.
Related
- Runtime targets — what's behind the URL.
- Secrets and env vars — the full list of injected variables.