Custom domains
Serve an app at your own domain, with HTTPS. Every app always keeps its own address at <app>.deplane.app.
Connect a domain
In the dashboard, open the app's Domains tab and add the hostname, or run deplane domains add www.example.com. Deplane shows the two DNS records to set at your DNS provider:
| Domain | Records |
|---|---|
A subdomain, such as www.example.com | CNAME to <app>.deplane.app, and TXT _deplane-challenge.www.example.com with the value shown |
A root domain, such as example.com (it cannot have a CNAME) | A to Deplane's address, and TXT _deplane-challenge.example.com with the value shown |
Deplane checks every 2 minutes for 48 hours (or press Check now). When DNS points at the app and the TXT value matches, Deplane routes the domain and gets a certificate from Let's Encrypt, usually within a minute: Waiting for DNS, then Issuing certificate, then Active. Certificates renew automatically.
Switch DNS on Cloudflare
If the domain's DNS is on Cloudflare, the CLI can set the records for you, show you the exact changes first, and undo them:
$ export CLOUDFLARE_API_TOKEN=... # or pipe it in
$ deplane domains switch www.example.com --app shop --check # preview, changes nothing
$ deplane domains switch www.example.com --app shop # asks, then switches
$ deplane domains switch www.example.com --rollback deplane-dns-www.example.com-20261031-140501.json
The token. Create a Cloudflare API token at https://dash.cloudflare.com/profile/api-tokens with the Edit zone DNS template, and under Zone Resources pick only this domain's zone. The CLI reads it from stdin or CLOUDFLARE_API_TOKEN, never from a flag. It talks to Cloudflare itself, from your computer: the token is never sent to Deplane, never printed, and never written to a file. If the token can see more than one zone, the preview says so: a token for the one zone is safer. You can delete the token in Cloudflare when you are done. A piped token takes stdin, so the switch cannot ask its question there and needs --yes: run it with --check first to read the preview, or set CLOUDFLARE_API_TOKEN and answer the question in your terminal.
The preview (--check) shows the records now at the domain and at _deplane-challenge.<domain>, and each change with its reason:
- The
TXTrecord that proves the domain is yours comes first, unless it is already there. - A subdomain gets a
CNAMEto<app>.deplane.app. An existing CNAME is updated in place, for example one that still points at your old host. AnyAorAAAArecord at the name conflicts with it and is deleted. - A root domain gets a
CNAMEtoo: Cloudflare flattens it, answering with Deplane's addresses and following them if they change. ItsAandAAAArecords are deleted. If<app>.deplane.appdoes not resolve to Deplane's edge addresses alone, the root getsArecords to them instead. - A subdomain that also has other records (an
MX, aTXT) getsArecords to Deplane's addresses instead of a CNAME, which would hide those records from some resolvers.Arecords name today's addresses: if Deplane announces new ones, run the switch again. - Records that Cloudflare proxies (orange cloud) are flagged: the new records are DNS only, so Cloudflare's WAF, caching, DDoS protection, Workers routes and Page Rules stop applying to that name. Deplane serves HTTPS itself and checks that the name points at it, which a proxied name does not.
MXrecords, every other record type at the name, and every other name in the zone are never changed. Only the domain you name and its_deplane-challengerecord are read, plus theNSandCAArecords above it.- If the records that change have a long TTL, the preview recommends setting it to Auto in Cloudflare first and waiting that long, so that the switch and a rollback both take effect within minutes. The new records use Auto (5 minutes).
- A
CAArecord that allows certificate authorities but not Let's Encrypt is flagged: Deplane could not get the certificate.
If the domain is not on the app yet, the switch adds it first. A zone that is not active on Cloudflare yet (its nameservers are not Cloudflare's) is refused, and so is a name delegated to other nameservers: records there do nothing.
The switch asks for confirmation (--yes skips it; with --json the preview goes to stderr before the question), then:
- Saves the records as they are to
deplane-dns-<domain>-<date>.jsonin the current folder, readable only by you, without the token, and flushed to disk. If the records changed while you were reading the preview, it stops instead (dns_records_changed). - Adds the
TXTrecord on its own, and waits (up to 3 minutes) until public DNS shows it. Nothing visitors see has changed yet. - Changes the records at the domain in one request to Cloudflare's batch API, which applies them all together or not at all: a failure leaves the domain as it was. If the zone cannot use the batch API, the changes are made one by one, adding before removing wherever Cloudflare allows it, and the command says so.
- Waits (up to 5 minutes) for Deplane to verify the domain, and says when it has. Until Deplane sees the new records (a few minutes, or up to the old TTL where resolvers cached the old answer) and gets the certificate, HTTPS to the domain may show errors.
--no-wait skips both waits, with a warning. If a change fails, the command stops and shows the rollback command.
The rollback (--rollback FILE) puts the saved records back exactly: content, TTL, proxy setting and comment, in one batch where Cloudflare allows it. It deletes what the switch created, restores what it changed, and recreates what it deleted (with new Cloudflare ids; tags on deleted records are not saved), starting from the records as they are now, so it also undoes a switch that stopped part way and is safe to run twice. --check previews it. The domain stays on the app; remove it with deplane domains remove if you no longer want it.
The dashboard does not do this yet, so that the token stays on your computer: use the CLI. Other DNS providers are not supported yet; set the records shown above by hand.
Why the TXT record
A domain is connected only when DNS proves that whoever controls it chose this connection. The TXT value is new each time a domain is added, so it proves the records were set for this app and this team, not left over from an app someone deleted: nobody can take over a domain whose DNS still points at Deplane after its owner moved on.
The TXT record is needed to connect the domain. After that you may keep it or remove it: a connected domain stays connected while its CNAME names the app (or, for a root domain, its A record points at Deplane).
Re-checks
Deplane checks every connected domain again every 6 hours. If its DNS no longer points at the app, the domain shows DNS changed and your team gets an alert (Custom domain no longer points here). Deplane keeps serving it for 7 days, sends a second alert a day before the end, and then stops serving the domain and its certificate; it goes back to Waiting for DNS. When DNS points back at the app, it is served again on the next check. A DNS server that does not answer changes nothing.
If another Deplane team adds the domain and proves it with its own TXT value (the domain changed hands, and its new owner points it at their app), that team's app gets the domain at once and the old connection stops. Your team gets an alert (Custom domain taken over) saying what happened and what to check: anyone who can write the domain's _deplane-challenge TXT record while it points at Deplane can do this, so keep that record's DNS access to your own people.
Limits
Up to 10 custom domains per app, and your plan's number for the whole team, all apps together: 3 on Free, 5 on Hobby, 25 on Pro, 100 on Team (see limit_domains). A domain can be connected to one app on Deplane at a time. Deplane's own domains cannot be added. Removing a domain stops it serving the app right away; deleting an app frees its domains.
When a team removes a domain or deletes its app, another team cannot add that domain while its CNAME still points at the old app's address (domain_dangling): change the CNAME first. A deleted app's name stays reserved for its team for 90 days, so nothing that still points at <app>.deplane.app (DNS, OAuth callbacks, webhooks) reaches another team's app.
This page is also available as Markdown. AI agents can start at /llms.txt.