Skip to main content
Tensor9 manages DNS hostnames, load balancers, and TLS certificates across cloud and Kubernetes environments. When you define these resources in your origin stack, Tensor9 compiles them to the appropriate service equivalent for the target 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 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:
1

Specify a vanity domain during app creation

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.
2

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.

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:
1

Assigns a unique subdomain

Tensor9 assigns a unique subdomain to the install (e.g., customer-a.ai-chat.playground.tensor9.app).
2

Creates a hosted zone

The appliance setup process creates a hosted zone for this subdomain within the customer’s environment.
3

Delegates the subdomain

Tensor9 delegates the subdomain from your root hosted zone to the appliance’s hosted zone.

Customer-Supplied Domains

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

Customer provides domain

During appliance setup, the customer specifies their desired domain (e.g., portal.corp.com).
2

Zone creation

The appliance setup process creates a hosted zone for this domain within the customer’s environment.
3

Customer delegation

The customer adds an NS record at their DNS provider to delegate the domain to their appliance’s hosted zone.

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 EnvironmentDNS ProviderNotes
AWSRoute 53Native AWS DNS service
Google CloudCloud DNSNative GCP DNS service
AzureAzure DNSNative Azure DNS service
Private KubernetesRoute 53 or CloudflareExternal 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.
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.

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.
# @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.
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]
}
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 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:
1

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.
2

Maps to target equivalents

Each load balancer resource is mapped to its functional equivalent based on the target form factor.
3

Preserves configuration

Port mappings, health checks, routing rules, and TLS settings are preserved in the mapping.
4

Manages dependencies

Required infrastructure (Traefik, cert-manager) is declared as service dependencies and installed automatically in the customer’s environment.

Equivalence Table

Origin (AWS)Private KubernetesNotes
Kubernetes Service with NLB annotationsKubernetes Service with MetalLB annotationsL4 TCP/UDP load balancing
Kubernetes Ingress with ALB annotationsTraefik IngressRouteL7 HTTP/HTTPS routing
Health check annotationsKubernetes probesAutomatic mapping
ACM certificate (via annotation)cert-manager CertificateSee TLS Certificates
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.

Origin Stack Example

Here is a typical Kubernetes service with AWS NLB annotations from an AWS origin stack:
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.
# @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 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:
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 KubernetesNotes
aws_acm_certificatecert-manager Certificate CRDDomain name and SANs preserved
aws_acm_certificate_validationRemovedcert-manager handles validation automatically
aws_route53_record (validation)RemovedDNS-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.
# @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

AnnotationApplies ToDescription
@vanity_domain_root()variableInjects 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.
# @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

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.
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.
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.
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.