# Custom Domains

Every service gets a platform hostname automatically on deploy (`{service}.tawa.pro`,
`{service}.sandbox.tawa.pro`, `{service}.uat.tawa.pro`). To put your own domain in front of a
service, use `tawa domain`. There are **three DNS modes**, picked by how the domain's DNS is
managed and whether Cloudflare can be used.

## Mode comparison

| | `--cloudflare` | `--external` | `--direct` (BYO-DNS) |
|---|---|---|---|
| Customer DNS record | CF auto-creates CNAME | CNAME → platform host | **A record → ingress LB IP** |
| TLS terminated at | Cloudflare edge | Cloudflare edge | **In-cluster (cert-manager / Let's Encrypt)** |
| Apex/root domains | CF flattening | hard (apex CNAME) | **native (A record is legal at apex)** |
| CDN / WAF / DDoS | yes | yes | no (origin hit directly) |
| `verify` checks for | n/a (auto) | `CNAME → target` | `A → ingress LB IP` |
| Needs CF zone / NS on CF | yes | yes (for TLS) | **no** |

**Default to `--cloudflare`.** Use `--direct` only when the customer cannot move their
nameservers to Cloudflare (e.g. DNS stuck at HostGator/cPanel) but can still set an A record.

## Commands

```bash
# Cloudflare-managed (auto DNS + TLS) — preferred
tawa domain add portal.example.com --cloudflare

# External CNAME (you add the CNAME; TLS still at Cloudflare)
tawa domain add portal.example.com --external

# Direct A record (BYO-DNS, TLS issued in-cluster) — for when Cloudflare is not an option
tawa domain add example.com --direct
tawa domain verify example.com
tawa deploy --prod

tawa domain list
tawa domain status example.com
tawa domain remove example.com
```

## Direct mode (BYO-DNS) — how it works

1. `tawa domain add <domain> --direct` registers the domain on the service with
   `dnsProvider: 'direct'`. The `expectedTarget` is the cluster **ingress LoadBalancer IP**
   (`INGRESS_TARGET` on the builder). The CLI prints the A record to add.
2. The customer adds an **A record** for the domain (and `www`, registered separately) pointing
   at that IP. Apex domains work natively — no CNAME flattening needed.
3. `tawa domain verify <domain>` checks that the **A record resolves to the ingress LB IP**
   (not a CNAME). On success the record is marked `dnsVerified`.
4. `tawa deploy` injects the verified direct-mode hosts into the service's **builder-managed
   ingress**, adds a `tls` block (`secretName: {service}-custom-tls`) and the annotation
   `cert-manager.io/cluster-issuer: {CERT_MANAGER_CLUSTER_ISSUER}` (default `letsencrypt-prod`).
5. **cert-manager** solves a Let's Encrypt **HTTP-01** challenge over port 80 (which works
   precisely because the A record already lands on the ingress), issues the cert into the TLS
   secret, and **auto-renews** it (~30 days before expiry). No Cloudflare, no manual cert work.

Cloudflare/external domains are **not** given an in-cluster TLS block — Cloudflare terminates
their TLS. Only `direct` domains get cert-manager TLS.

## Platform requirements for direct mode

- **cert-manager** must be installed in the cluster with a `ClusterIssuer` named to match
  `CERT_MANAGER_CLUSTER_ISSUER` (default `letsencrypt-prod`, HTTP-01 solver on `ingressClassName: nginx`).
- **`INGRESS_TARGET`** on the builder must be set to the ingress LB IP. `tawa domain add --direct`
  fails with `DIRECT_MODE_UNAVAILABLE` if it is unset or not an IP.

## Trade-offs / operational notes

- **LB IP coupling.** Direct-mode customers hardcode the ingress LB IP in their DNS. If that IP
  ever changes (cluster migration, LB recreation) every direct-mode domain breaks. Pin a
  **reserved/floating IP** for the ingress LoadBalancer before relying on this at scale.
- **No edge protection.** Direct-mode traffic hits the origin raw — no Cloudflare CDN, WAF, or
  DDoS shielding, and the origin IP is public. Fine for static marketing sites; weigh it for
  anything sensitive.
- **Renewal dependency.** HTTP-01 re-validates every ~60 days, so the A record must stay pointed
  at the platform and port 80 must remain reachable.
- TLS for non-direct hosts on the same ingress is unaffected — nginx only force-redirects /
  serves certs for hosts present in a `tls` block.

## Apex + www

Direct mode registers one record per hostname. For apex + www, add both:

```bash
tawa domain add example.com --direct
tawa domain add www.example.com --direct
tawa domain verify example.com && tawa domain verify www.example.com
tawa deploy --prod
```

Both hosts are added to the same `{service}-custom-tls` secret, so a single Let's Encrypt cert
covers them.
