> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tensor9.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Endpoints, DNS, and Load Balancing

Tensor9 manages DNS hostnames, load balancers, and TLS certificates across cloud and Kubernetes environments. When you define these resources in your [origin stack](/fundamentals/origin-stacks), Tensor9 compiles them to the appropriate [service equivalent](/fundamentals/service-equivalents) for the target [form factor](/fundamentals/key-concepts#form-factor). This includes private Kubernetes clusters where cloud-managed services aren't available.

You can also assign publicly-accessible custom DNS hostnames to deployed [appliances](/fundamentals/appliances) by configuring a **vanity domain** for your app. This enables touch-free SSL certificate generation and branded endpoints for your customers.

There are two ways to assign vanity domains to appliances:

1. **Vendor-Supplied**: Tensor9 automatically assigns subdomains of the app's root vanity domain to customers (e.g., `customer-a.ai-chat.playground.tensor9.app`).
2. **Customer-Supplied**: Your customer brings their own domain (e.g., `app.internal.customer.com`) and delegates it to the appliance.

In both cases, your Terraform origin stack remains exactly the same. Tensor9 creates the hosted zone during appliance setup *before* the deployment stack is deployed. The deployment stack simply looks up the pre-existing zone to create records.

## Enable Vanity Domains

To enable vanity domains, specify a root domain during app creation:

<Steps>
  <Step title="Specify a vanity domain during app creation">
    ```bash theme={null}
    tensor9 app create \
      -name ai-chat \
      -vanityDomain ai-chat.playground.tensor9.app
    ```

    This triggers Tensor9 to create a hosted zone for this domain. The vanity domain is scoped to the app and all installs of this app share the same root domain.
  </Step>

  <Step title="One-time manual delegation">
    Perform a one-time manual delegation from your DNS provider to the nameservers of the hosted zone created by Tensor9. This allows Tensor9 to manage subdomains on your behalf.
  </Step>
</Steps>

## Vanity Domain Assignment

Once the app is created with a vanity domain, your customers can choose to use either the vendor-supplied or customer-supplied vanity domain option during appliance setup.

### Vendor-Supplied Domains

In this model, Tensor9 automatically manages the assignment and delegation of a subdomain of your app's root vanity domain to an appliance.

When a customer installs your app, Tensor9 automatically:

<Steps>
  <Step title="Assigns a unique subdomain">
    Tensor9 assigns a unique subdomain to the install (e.g., `customer-a.ai-chat.playground.tensor9.app`).
  </Step>

  <Step title="Creates a hosted zone">
    The appliance setup process creates a hosted zone for this subdomain within the customer's environment.
  </Step>

  <Step title="Delegates the subdomain">
    Tensor9 delegates the subdomain from your root hosted zone to the appliance's hosted zone.
  </Step>
</Steps>

### Customer-Supplied Domains

In this model, your customer brings their own domain name.

<Steps>
  <Step title="Customer provides domain">
    During appliance setup, the customer specifies their desired domain (e.g., `portal.corp.com`).
  </Step>

  <Step title="Zone creation">
    The appliance setup process creates a hosted zone for this domain within the customer's environment.
  </Step>

  <Step title="Customer delegation">
    The customer adds an NS record at their DNS provider to delegate the domain to their appliance's hosted zone.
  </Step>
</Steps>

## DNS Provider Support

Tensor9 uses the **native DNS service** of each target environment where possible. For environments without a built-in DNS service, Tensor9 supports external DNS providers.

| Target Environment | DNS Provider           | Notes                                                   |
| ------------------ | ---------------------- | ------------------------------------------------------- |
| AWS                | Route 53               | Native AWS DNS service                                  |
| Google Cloud       | Cloud DNS              | Native GCP DNS service                                  |
| Azure              | Azure DNS              | Native Azure DNS service                                |
| Private Kubernetes | Route 53 or Cloudflare | External DNS provider configured during appliance setup |

Your origin stack doesn't change based on the DNS provider. During compilation, Tensor9 handles provider selection automatically based on the target form factor.

<Note>
  For private Kubernetes appliances, the DNS provider is configured at the appliance level. Credentials are stored securely, never leave the customer's environment, and are used by Tensor9 to manage DNS records on their behalf.
</Note>

## Origin Stack: DNS

To support both vanity domain workflows, your Terraform code should **look up** a hosted zone rather than create one.

### 1. Receive the Domain Name

Use the `@vanity_domain_root()` annotation on a variable. Tensor9 injects the install's assigned fully qualified domain name (FQDN) at deploy time.

```hcl theme={null}
# @vanity_domain_root()
variable "domain_root" {
  type        = string
  default     = "local.example.com"
}
```

### 2. Look Up the Hosted Zone

Reference the hosted zone using a Terraform **data source** instead of a resource. The zone already exists (created during appliance setup) by the time your stack is deployed.

<CodeGroup>
  ```hcl AWS theme={null}
  data "aws_route53_zone" "app_zone" {
    name = var.domain_root
  }

  resource "aws_route53_record" "app" {
    zone_id = data.aws_route53_zone.app_zone.zone_id
    name    = "www.${var.domain_root}"
    type    = "CNAME"
    ttl     = 300
    records = [aws_lb.app.dns_name]
  }
  ```

  ```hcl GCP theme={null}
  data "google_dns_managed_zone" "app_zone" {
    name = var.domain_root
  }

  resource "google_dns_record_set" "app" {
    managed_zone = data.google_dns_managed_zone.app_zone.name
    name         = "www.${var.domain_root}."
    type         = "CNAME"
    ttl          = 300
    rrdatas      = [google_compute_global_address.app.address]
  }
  ```

  ```hcl Azure theme={null}
  data "azurerm_dns_zone" "app_zone" {
    name = var.domain_root
  }

  resource "azurerm_dns_cname_record" "app" {
    name                = "www"
    zone_name           = data.azurerm_dns_zone.app_zone.name
    resource_group_name = var.resource_group_name
    ttl                 = 300
    record              = azurerm_public_ip.app.fqdn
  }
  ```
</CodeGroup>

During compilation, all DNS records are compiled to use the DNS provider configured on the appliance. For private Kubernetes targets, Route 53 and Cloudflare records are managed automatically via the configured DNS provider.

## Load Balancer Endpoints

Tensor9 compiles load balancer resources from your origin stack to equivalent resources in the target environment using the [service equivalents](/fundamentals/service-equivalents) model. For example, you can define load balancers using AWS resources, and Tensor9 will map them to Kubernetes-native load balancing when compiling for private environments.

### How It Works

Vendors define load balancer resources in their origin stack using AWS convention. During compilation, Tensor9:

<Steps>
  <Step title="Identifies load balancer resources">
    Tensor9 detects the use of NLBs, ALBs, or Kubernetes Services with AWS Load Balancer Controller annotations and Ingress resources with load balancer annotations.
  </Step>

  <Step title="Maps to target equivalents">
    Each load balancer resource is mapped to its functional equivalent based on the target form factor.
  </Step>

  <Step title="Preserves configuration">
    Port mappings, health checks, routing rules, and TLS settings are preserved in the mapping.
  </Step>

  <Step title="Manages dependencies">
    Required infrastructure (Traefik, cert-manager) is declared as service dependencies and installed automatically in the customer's environment.
  </Step>
</Steps>

### Equivalence Table

| Origin (AWS)                            | Private Kubernetes                          | Notes                                     |
| --------------------------------------- | ------------------------------------------- | ----------------------------------------- |
| Kubernetes Service with NLB annotations | Kubernetes Service with MetalLB annotations | L4 TCP/UDP load balancing                 |
| Kubernetes Ingress with ALB annotations | Traefik IngressRoute                        | L7 HTTP/HTTPS routing                     |
| Health check annotations                | Kubernetes probes                           | Automatic mapping                         |
| ACM certificate (via annotation)        | cert-manager Certificate                    | See [TLS Certificates](#tls-certificates) |

<Note>
  You do not need to define Traefik or cert-manager resources in your origin stack. Tensor9 automatically declares these as service dependencies and manages their installation in the target environment.
</Note>

### Origin Stack Example

Here is a typical Kubernetes service with AWS NLB annotations from an AWS origin stack:

```hcl theme={null}
resource "kubernetes_service_v1" "app" {
  metadata {
    name      = "app-lb"
    namespace = var.namespace

    annotations = {
      "service.beta.kubernetes.io/aws-load-balancer-type"   = "nlb"
      "service.beta.kubernetes.io/aws-load-balancer-scheme" = "internet-facing"
    }
  }

  spec {
    type = "LoadBalancer"

    port {
      port        = 443
      target_port = 8080
      protocol    = "TCP"
    }

    selector = {
      app = "my-app"
    }
  }
}
```

When compiled for a private Kubernetes form factor, Tensor9 replaces the AWS annotations with MetalLB annotations and adds the necessary service dependency declarations. The port mappings and selectors are preserved.

### `@kubernetes_service()` Annotation

Use `@kubernetes_service(type='LoadBalancer')` on a `data "kubernetes_service_v1"` data source when your stack needs to reference a load balancer service's external address. This annotation tells the compiler how to handle platform-specific behavior. For example, MetalLB assigns IP addresses (not hostnames), so the compiler creates a DNS A record and rewrites `.hostname` references accordingly.

```hcl theme={null}
# @kubernetes_service(type='LoadBalancer')
data "kubernetes_service_v1" "traefik" {
  metadata {
    name      = "traefik"
    namespace = "traefik"
  }
}

resource "aws_route53_record" "app" {
  zone_id = data.aws_route53_zone.app_zone.zone_id
  name    = "app.${var.domain_root}"
  type    = "CNAME"
  ttl     = 300
  records = [data.kubernetes_service_v1.traefik.status.0.load_balancer.0.ingress.0.hostname]
}
```

## TLS Certificates

Tensor9 compiles TLS certificate resources to the appropriate equivalent for each target environment. In AWS, certificates are managed by ACM. In private Kubernetes environments, Tensor9 uses [cert-manager](https://cert-manager.io/) with Let's Encrypt DNS-01 validation.

### Origin Stack: ACM Certificates

Define TLS certificates using AWS ACM resources. Since the vanity domain is properly delegated, you can use automatic DNS validation:

```hcl theme={null}
resource "aws_acm_certificate" "cert" {
  domain_name       = "www.${var.domain_root}"
  validation_method = "DNS"
}

resource "aws_route53_record" "cert_validation" {
  for_each = {
    for dvo in aws_acm_certificate.cert.domain_validation_options : dvo.domain_name => {
      name   = dvo.resource_record_name
      record = dvo.resource_record_value
      type   = dvo.resource_record_type
    }
  }

  allow_overwrite = true
  name            = each.value.name
  records         = [each.value.record]
  ttl             = 60
  type            = each.value.type
  zone_id         = data.aws_route53_zone.app_zone.zone_id
}

resource "aws_acm_certificate_validation" "cert" {
  certificate_arn         = aws_acm_certificate.cert.arn
  validation_record_fqdns = [for record in aws_route53_record.cert_validation : record.fqdn]
}
```

### Equivalence: cert-manager

When compiling for private Kubernetes, Tensor9 replaces ACM resources with cert-manager equivalents:

| Origin (AWS)                      | Private Kubernetes             | Notes                                           |
| --------------------------------- | ------------------------------ | ----------------------------------------------- |
| `aws_acm_certificate`             | cert-manager `Certificate` CRD | Domain name and SANs preserved                  |
| `aws_acm_certificate_validation`  | Removed                        | cert-manager handles validation automatically   |
| `aws_route53_record` (validation) | Removed                        | DNS-01 challenge handled by cert-manager solver |

The cert-manager `ClusterIssuer` is managed automatically by Tensor9's service dependency system and vendors do not need to define it. The issuer uses Let's Encrypt with DNS-01 validation via the appliance's configured DNS provider.

### Referencing External Certificates

When an ACM certificate is passed as a variable (rather than defined in-stack), the compiler cannot follow the reference to extract domain information. Instead, use a `data "aws_acm_certificate"` data source to look up the certificate by domain. This allows Tensor9 to extract the domain information and automatically convert it to a Kubernetes data source when compiling for private Kubernetes.

```hcl theme={null}
# @vanity_domain_root()
variable "domain_root" {
  description = "Root domain name (e.g., example.com)"
  type        = string
}

data "aws_acm_certificate" "cert" {
  domain   = var.domain_root
  statuses = ["ISSUED"]
}
```

## Annotations Reference

| Annotation                                 | Applies To                     | Description                                                      |
| ------------------------------------------ | ------------------------------ | ---------------------------------------------------------------- |
| `@vanity_domain_root()`                    | `variable`                     | Injects the install's vanity domain FQDN at deploy time          |
| `@vanity_domain_provided()`                | `variable` (bool)              | Controls conditional hosted zone creation (see below)            |
| `@kubernetes_service(type='LoadBalancer')` | `data "kubernetes_service_v1"` | Marks a load balancer data source for platform-specific handling |

### Additional Annotations

#### @vanity\_domain\_provided()

If you need to keep the hosted zone definition inside your origin stack (e.g., for self-contained single-file stacks), use `@vanity_domain_provided()` to handle this conditionally.

Annotate a boolean variable with `@vanity_domain_provided()`. Set it to `true` to allow the hosted zone to be created when you deploy the stack directly.

During compilation, Tensor9 sets the variable to `false` to skip hosted zone creation (since it was created during appliance setup) and allows the deployment stack to look up the zone instead.

```hcl theme={null}
# @vanity_domain_provided()
variable "zone_provided" {
  type    = bool
  default = false
}

# Conditionally create the zone ONLY if it wasn't provided
resource "aws_route53_zone" "created_zone" {
  count = var.zone_provided ? 0 : 1
  name  = var.domain_root
}

# Look up the zone if it WAS provided
data "aws_route53_zone" "lookup_zone" {
  count = var.zone_provided ? 1 : 0
  name  = var.domain_root
}
```

## Best Practices

<AccordionGroup>
  <Accordion title="Purchase a separate root domain">
    If your app is hosted at `saas.com`, pick an alternative like
    `saas-customers.com` or `saas.app` for your vanity domain root. This ensures that the cookie space for your
    hosted offering is entirely separate from your customer's appliances.
  </Accordion>

  <Accordion title="Keep the root domain as brief as possible">
    The maximum length of the Common Name in a certificate is 64 characters. Vanity
    domains assigned to customers include the appliance ID as well as any
    endpoints defined in your origin stack. The total length of the
    subdomain for which a certificate is requested cannot exceed the limit.
  </Accordion>

  <Accordion title="Use data sources for hosted zones">
    Always reference hosted zones using data sources (`data "aws_route53_zone"`) instead of creating them with resources. The zone is created during appliance setup before your stack is deployed. Using a data source ensures your stack works with both vendor-supplied and customer-supplied vanity domains.
  </Accordion>

  <Accordion title="Let Tensor9 manage load balancer equivalence">
    Do not manually define Traefik or cert-manager resources in your origin stack. Use standard AWS load balancer annotations and ACM certificates — Tensor9 compiles these to the correct equivalents for each target environment and manages the required infrastructure automatically.
  </Accordion>
</AccordionGroup>

## Questions You Might Ask

<AccordionGroup>
  <Accordion title="How do vendor-supplied and customer-supplied vanity domains differ?">
    Both modes create a DNS hosted zone in the customer's cloud account during appliance setup, and
    both flow through the same
    [`@vanity_domain_root()`](/fundamentals/endpoints#vanity_domain_root) variable in your origin stack.
    Your origin stack does not need to distinguish between the two.

    The difference is in who owns the domain and who performs the NS delegation:

    * **Vendor-supplied**: You register a domain (e.g., `your-domain.app`) and delegate it to Tensor9.
      Tensor9 auto-generates a subdomain for each customer (e.g., `abc123.your-domain.app`) and
      handles NS delegation to the customer's hosted zone automatically.
    * **Customer-supplied**: The customer provides their own domain during appliance setup and
      creates the NS delegation on their side. Tensor9 configures the customer's hosted zone accordingly.
  </Accordion>

  <Accordion title="Where is the customer's hosted zone created?">
    In the customer's cloud account, during appliance setup. For vendor-supplied domains, Tensor9
    then delegates the subdomain from your app's root hosted zone (in your Tensor9 Cloud Account)
    to the customer's hosted zone by creating NS records automatically. For customer-supplied
    domains, the customer handles NS delegation themselves.

    Here is the delegation chain for vendor-supplied domains:

    <img src="https://mintcdn.com/tensor9/ALWb_MOQ48vbBjRX/images/diagrams/vanity-domain-dns-light.svg?fit=max&auto=format&n=ALWb_MOQ48vbBjRX&q=85&s=cbcea6f9005b5f36ed3807603ebace22" className="block dark:hidden" alt="Vanity domain DNS delegation chain" width="780" height="200" data-path="images/diagrams/vanity-domain-dns-light.svg" />

    <img src="https://mintcdn.com/tensor9/ALWb_MOQ48vbBjRX/images/diagrams/vanity-domain-dns-dark.svg?fit=max&auto=format&n=ALWb_MOQ48vbBjRX&q=85&s=4e87b2a1cb666d58496164a72f792c44" className="hidden dark:block" alt="Vanity domain DNS delegation chain" width="780" height="200" data-path="images/diagrams/vanity-domain-dns-dark.svg" />

    Your domain registrar delegates to the Tensor9-managed hosted zone in your Tensor9 Cloud Account.
    Tensor9 then delegates each customer's subdomain to their own hosted zone in their account.
  </Accordion>

  <Accordion title="Who creates the TLS certificates for each customer?">
    You define a TLS certificate resource once in your origin stack, referencing the
    [`@vanity_domain_root()`](/fundamentals/endpoints#vanity_domain_root) annotated domain variable.
    Tensor9 compiles that resource into each customer's deployment with the correct domain and the
    appropriate certificate type for the target environment (e.g., AWS ACM certificates on AWS,
    cert-manager Certificates on Kubernetes). This works the same for both vendor-supplied and
    customer-supplied domains.

    Do not hardcode certificate identifiers - the compiler operates on Terraform resources, not raw strings.
  </Accordion>

  <Accordion title="Do TLS certificates live in our account or the customer's account?">
    In the customer's account, alongside the load balancers and CDN distributions that reference them.
    DNS validation records are created in the customer's hosted zone during deployment. Because
    the subdomain is delegated to the customer's zone, certificate authorities can resolve the
    validation records through the delegation chain.

    <img src="https://mintcdn.com/tensor9/LOT8cg6etuPWKljy/images/diagrams/acm-cert-flow-light.svg?fit=max&auto=format&n=LOT8cg6etuPWKljy&q=85&s=77cd44448b73a602b83181cabf784b21" className="block dark:hidden" alt="TLS certificate and DNS validation flow" width="700" height="200" data-path="images/diagrams/acm-cert-flow-light.svg" />

    <img src="https://mintcdn.com/tensor9/LOT8cg6etuPWKljy/images/diagrams/acm-cert-flow-dark.svg?fit=max&auto=format&n=LOT8cg6etuPWKljy&q=85&s=55d5ebb9cc913a2ea4c0c8d184297a04" className="hidden dark:block" alt="TLS certificate and DNS validation flow" width="700" height="200" data-path="images/diagrams/acm-cert-flow-dark.svg" />
  </Accordion>

  <Accordion title="Do I need to create the root hosted zone myself?">
    No. For vendor-supplied domains, Tensor9 creates it automatically during app creation. You
    perform a one-time NS delegation so that DNS queries for your vanity domain resolve to the
    Tensor9-managed hosted zone. Where you configure that delegation depends on the domain you chose:

    * **Root domain** (e.g., `your-domain.app`): Update the NS records at your domain registrar to point
      to the nameservers Tensor9 returns.
    * **Subdomain** (e.g., `self-host.your-domain.app`): Create an NS record in whatever DNS provider
      hosts the parent domain (e.g., your `your-domain.app` zone) delegating the subdomain to the Tensor9
      nameservers.

    For customer-supplied domains, the customer creates and delegates their own hosted zone during
    appliance setup.
  </Accordion>

  <Accordion title="Can I change the vanity domain after app creation?">
    No. To use a different vanity domain, create a new app with the desired domain.
  </Accordion>

  <Accordion title="Should I use a separate domain for customer deployments?">
    Yes, for vendor-supplied domains. If your cloud product runs at `your-domain.com`, use a
    separate domain like `your-domain.app` for vanity domains. This keeps the cookie space for
    your cloud offering entirely separate from customer appliances.
  </Accordion>
</AccordionGroup>

## Related Topics

* [**Service Equivalents**](/fundamentals/service-equivalents): Full reference for how Tensor9 maps services across cloud providers
* [**Origin Stacks**](/fundamentals/origin-stacks): How to define portable infrastructure code
* [**Deployments**](/fundamentals/deployments): How compilation and deployment works
* [**Appliances**](/fundamentals/appliances): Deploy to different cloud environments
