Custom domains and HTTPS
This guide assumes the platform is configured with a Gateway and a clusterIssuer; if not, start with Exposing apps and HTTPS.
-
Add the domain to the app. Open the app, Settings tab. Under Domains, add
api.shop.example.comto Custom domains and press Save. Or send the full list:Terminal window curl -fsS -X PATCH "$KUBEN_URL/api/v1/projects/shop/environments/production/apps/api" \-H "Authorization: Bearer $KUBEN_TOKEN" -H 'Content-Type: application/json' \-d '{"domains": ["api.shop.example.com"]}' -
Point DNS at the gateway. Create an
A/AAAArecord (or aCNAME) for the domain pointing at the Gateway’s address.kubectl -n kuben-system get gateway kubenshows it. -
Check it. Press Check DNS in the same Domains section (
GET …/apps/api/domains). For every hostname it reports:Status Meaning okresolves to the gateway mismatchresolves somewhere else unresolvedno record yet, or not propagated unknownthe gateway has no address to compare with In a production environment, the domain change is a deployment like any other and waits for its approval first.
-
Wait for the certificate. Kuben adds an HTTPS listener for the hostname to its Gateway, and cert-manager issues the certificate through the HTTP-01 challenge. Plain HTTP requests are redirected to HTTPS. Within a minute or two
https://api.shop.example.comanswers.
A domain belongs to one app
Section titled “A domain belongs to one app”Kuben refuses the same domain on a second app with 409 Conflict. At the gateway, each hostname only accepts routes from the namespace that claimed it first, so one tenant cannot take over another tenant’s domain by adding it to their own app.
Claim your domain
Section titled “Claim your domain”An organization claims a domain before its apps use it. A claim on example.com covers the domain itself and every name below it, such as shop.example.com and api.example.com.
- Verified is exclusive. Once a claim is verified, no other organization on the installation can verify an overlapping name or serve a host below it. Kuben checks this in one transaction, so two organizations can never both win.
- Optional by default. Unclaimed domains still work. With
domains.require_claim = true, an app can only use domains its organization has verified. Either way, a domain another organization verified is refused.
In the console, open Domains in the sidebar and press Claim under Claims. Claims and DNS provider accounts belong to the organization, so only owners manage them. With the API:
curl -X POST -H "Authorization: Bearer $TOKEN" -H 'content-type: application/json' \ -d '{"domain":"example.com"}' https://kuben.example.com/api/v1/domainsThe answer names a TXT record, _kuben-challenge.example.com, and the value it must hold. There are two ways to prove the claim:
- With a TXT record. Create that record, then verify: in the console, Verify with The TXT record I added and press Verify, or
Kuben reads the record through DNS-over-HTTPS (
Terminal window curl -X POST -H "Authorization: Bearer $TOKEN" -H 'content-type: application/json' -d '{}' \https://kuben.example.com/api/v1/domains/<id>/verifydomains.doh_url), so what counts is the public DNS. If the record is missing or wrong, the claim stayspendingandlastErrorsays why. - With a DNS provider account. If one of your organization’s provider accounts holds the domain’s zone,
{"provider":"cf"}verifies the claim without a TXT record.
Revoking a claim (DELETE /api/v1/domains/<id>) removes the protection. Apps keep their domains.
DNS records through Cloudflare
Section titled “DNS records through Cloudflare”Add a Cloudflare account once, with an API token that has Zone:Read and DNS:Edit on your zones. In the console: Domains, DNS providers, enter a Name and the Cloudflare API token, and press Add provider. Adding one needs a signed-in session; API tokens are refused. The API call, from such a session:
curl -X POST -H 'X-Kuben-Client: curl' -b cookies.txt -H 'content-type: application/json' \ -d '{"name":"cf","kind":"cloudflare","token":"<api token>"}' \ https://kuben.example.com/api/v1/dns-providersKuben checks the token before saving it, stores it encrypted, and never shows it again.
Then write an app’s records: on the app’s Settings tab, under DNS records, pick the Provider and press Write records. Or:
curl -X POST -H "Authorization: Bearer $TOKEN" -H 'content-type: application/json' \ -d '{"provider":"cf"}' \ https://kuben.example.com/api/v1/projects/shop/environments/prod/apps/web/dnsEvery custom domain your organization has verified gets records pointing at the Gateway:
- By default: A and AAAA records for the Gateway’s public addresses.
- With
domains.cname_target: a CNAME to that name.
Kuben is careful with records it did not write:
- Tagging. Its records carry the comment
kuben:<organization>. - Conflicts. A record of the same name and type without that comment is reported as a
conflictand never overwritten. - Deletion. A record is deleted only when Cloudflare still reports the id Kuben stored for it.
- Retries. Running the call again is safe: records that are already right are reported
unchanged.
Wildcard and apex certificates (DNS-01)
Section titled “Wildcard and apex certificates (DNS-01)”HTTP-01 challenges need port 80 to reach the Gateway directly. For wildcard certificates, for apex names, or behind a proxy, use DNS-01 through Cloudflare:
kuben dns01-issuer --email ops@example.com --token-file /root/cloudflare-tokenkubectl patch kubenconfig kuben --type merge -p '{"spec":{"clusterIssuer":"letsencrypt-dns"}}'The command stores the token in a Secret next to cert-manager and creates the ClusterIssuer letsencrypt-dns. Add --staging to test against Let’s Encrypt’s staging server, or --dry-run to print the objects without applying them.
Doctor
Section titled “Doctor”For each custom domain, an app’s Doctor (Open Doctor on the app page) also reports:
- claim: whether your organization verified the domain.
- delegation: the zone’s public name servers.
- proxy: whether a provider proxies the record. A proxy means visitors reach the provider’s edge rather than your Gateway, so use DNS-01 certificates there.
Generated hostnames
Section titled “Generated hostnames”Every HTTP app also answers on <app>-<project>-<environment>.<baseDomain>, for example api-shop-production.apps.example.com. With many apps, provide a wildcard certificate for *.<baseDomain> (a DNS-01 Certificate) and name its Secret in the KubenConfig; all generated hostnames then share one listener instead of each taking one:
kubectl patch kubenconfig kuben --type merge -p '{"spec":{"wildcardTlsSecret":"<secret>"}}'Limits to know
Section titled “Limits to know”- A Gateway holds at most 64 listeners, and Kuben uses at most 60. Beyond that, use the wildcard listener for generated hosts. Custom domains still take a listener each; ListenerSet support (experimental in Gateway API) is on the roadmap.
- cert-manager must run with Gateway API support (
config.enableGatewayAPI=truein its Helm values). - TLS terminates at your Gateway controller (Traefik, Envoy Gateway, Cilium). Kuben configures it but never sees the traffic.