DNS Entries

The dns resource type configures DNS records for your environment services. Teabar automatically manages DNS records and cleans them up when environments are destroyed.

Basic Usage

spec:
  resources:
    dns:
      - name: app
        type: A
        target: "{{ .Resources.clusters.main.ingress_ip }}"

This creates an A record at app.<environment-domain> pointing to your cluster’s ingress IP.

Schema Reference

dns:
  - name: string              # Required: Record name (subdomain)
    type: string              # Required: Record type (A, AAAA, CNAME, TXT, MX)
    target: string            # Required: Record value (supports templating)
    enabled: boolean          # Optional: Enable/disable this record (default: true)
    ttl: integer              # Optional: TTL in seconds (default: 300)
    priority: integer         # Optional: Priority for MX records
    proxied: boolean          # Optional: Enable Cloudflare proxy (Cloudflare only)
    count: integer            # Optional: Create multiple records with {{ .Index }}
    dependsOn:                # Optional: Resource dependencies
      - string

Record Types

A Records

Point a hostname to an IPv4 address:

dns:
  - name: app
    type: A
    target: "{{ .Resources.clusters.main.ingress_ip }}"
    ttl: 300

  - name: api
    type: A
    target: "{{ .Resources.vms.api-server.public_ip }}"

AAAA Records

Point a hostname to an IPv6 address:

dns:
  - name: app
    type: AAAA
    target: "{{ .Resources.clusters.main.ingress_ipv6 }}"

CNAME Records

Create an alias to another hostname:

dns:
  - name: www
    type: CNAME
    target: app

  - name: docs
    type: CNAME
    target: "{{ .Environment.Domain }}"

Wildcard Records

Route all subdomains to a single target:

dns:
  - name: "*"
    type: A
    target: "{{ .Resources.clusters.main.ingress_ip }}"

  # Or as CNAME
  - name: "*.apps"
    type: CNAME
    target: app

TXT Records

Add text records for verification or configuration:

dns:
  - name: _dmarc
    type: TXT
    target: "v=DMARC1; p=none; rua=mailto:[email protected]"

  - name: "@"
    type: TXT
    target: "v=spf1 include:_spf.google.com ~all"

MX Records

Configure mail servers:

dns:
  - name: "@"
    type: MX
    target: mail.example.com
    priority: 10

  - name: "@"
    type: MX
    target: mail-backup.example.com
    priority: 20

Environment Domain

Every Teabar environment gets a unique domain under teabar.dev:

<environment-id>.teabar.dev

Your DNS records create subdomains under this:

dns:
  - name: app
    type: A
    target: "1.2.3.4"
# Creates: app.<environment-id>.teabar.dev

  - name: gitlab
    type: A
    target: "1.2.3.5"
# Creates: gitlab.<environment-id>.teabar.dev

Access the domain in templates:

# Reference in other resources
helm:
  - name: gitlab
    values:
      global:
        hosts:
          domain: "{{ .Environment.Domain }}"
          gitlab:
            name: "gitlab.{{ .Environment.Domain }}"

Dynamic DNS

Reference Other Resources

Create DNS records pointing to dynamically provisioned resources:

spec:
  resources:
    clusters:
      - name: main
        provider: hetzner
        type: talos
        features:
          - ingress-nginx

    vms:
      - name: bastion
        provider: hetzner
        type: cx21

    dns:
      # Cluster ingress
      - name: app
        type: A
        target: "{{ .Resources.clusters.main.ingress_ip }}"

      # Wildcard for all ingress routes
      - name: "*.app"
        type: CNAME
        target: app

      # VM access
      - name: bastion
        type: A
        target: "{{ .Resources.vms.bastion.public_ip }}"

Per-Participant Records

Create DNS records for each participant:

spec:
  variables:
    - name: participant_count
      type: integer
      default: 10

  resources:
    vms:
      - name: "workstation-{{ .Index }}"
        count: "{{ .Variables.participant_count }}"
        provider: hetzner
        type: cx21

    dns:
      - name: "p{{ .Index }}"
        type: A
        count: "{{ .Variables.participant_count }}"
        target: "{{ (index .Resources.vms (printf "workstation-%d" .Index)).public_ip }}"

This creates:

  • p0.<env-domain> → workstation-0 IP
  • p1.<env-domain> → workstation-1 IP

Common Patterns

GitLab with Subdomains

GitLab requires multiple DNS entries for full functionality:

dns:
  # Main GitLab
  - name: gitlab
    type: A
    target: "{{ .Resources.clusters.main.ingress_ip }}"

  # GitLab Pages
  - name: "*.pages"
    type: CNAME
    target: gitlab

  # Container Registry
  - name: registry
    type: CNAME
    target: gitlab

  # Mattermost (if enabled)
  - name: mattermost
    type: CNAME
    target: gitlab

Multi-Service Application

dns:
  # API gateway
  - name: api
    type: A
    target: "{{ .Resources.clusters.main.ingress_ip }}"

  # Frontend
  - name: www
    type: CNAME
    target: api

  # Root domain redirect
  - name: "@"
    type: CNAME
    target: www

  # Documentation
  - name: docs
    type: CNAME
    target: api

  # Admin panel
  - name: admin
    type: CNAME
    target: api

Separate Services on Different Clusters

spec:
  resources:
    clusters:
      - name: frontend
        provider: hetzner
        type: talos
        features:
          - ingress-nginx

      - name: backend
        provider: hetzner
        type: talos
        features:
          - ingress-nginx

    dns:
      - name: app
        type: A
        target: "{{ .Resources.clusters.frontend.ingress_ip }}"

      - name: api
        type: A
        target: "{{ .Resources.clusters.backend.ingress_ip }}"

TTL Configuration

Set appropriate TTL (Time To Live) values:

dns:
  # Short TTL for frequently changing services
  - name: dynamic
    type: A
    target: "{{ .Resources.vms.dynamic.public_ip }}"
    ttl: 60

  # Standard TTL for stable services
  - name: app
    type: A
    target: "{{ .Resources.clusters.main.ingress_ip }}"
    ttl: 300

  # Long TTL for static records
  - name: docs
    type: CNAME
    target: docs.example.com
    ttl: 3600

Provider Support

Teabar-Managed DNS

By default, Teabar manages DNS under the teabar.dev domain:

dns:
  - name: app
    type: A
    target: "1.2.3.4"
# Result: app.<env-id>.teabar.dev

Hetzner DNS

Use your own domain managed by Hetzner DNS:

spec:
  environment:
    dns:
      provider: hetzner
      zone: training.example.com

  resources:
    dns:
      - name: app
        type: A
        target: "1.2.3.4"
# Result: app.training.example.com

AWS Route 53

spec:
  environment:
    dns:
      provider: aws
      zone: Z1234567890ABC  # Hosted zone ID
      domain: training.example.com

  resources:
    dns:
      - name: app
        type: A
        target: "1.2.3.4"

Azure DNS

spec:
  environment:
    dns:
      provider: azure
      resourceGroup: my-dns-rg
      zone: training.example.com

  resources:
    dns:
      - name: app
        type: A
        target: "1.2.3.4"

Cloudflare

spec:
  environment:
    dns:
      provider: cloudflare
      zone: example.com

  resources:
    dns:
      - name: app
        type: A
        target: "1.2.3.4"
        proxied: true  # Enable Cloudflare proxy/CDN

DNS Outputs

Reference DNS records in other resources:

spec:
  resources:
    dns:
      - name: app
        type: A
        target: "{{ .Resources.clusters.main.ingress_ip }}"

    manifests:
      - name: app-config
        cluster: main
        template: |
          apiVersion: v1
          kind: ConfigMap
          metadata:
            name: app-urls
          data:
            app_url: "https://{{ .Resources.dns.app.fqdn }}"
            # Result: https://app.<env-id>.teabar.dev

Available DNS outputs:

OutputDescription
.fqdnFully qualified domain name
.nameRecord name (subdomain)
.typeRecord type
.targetRecord value
.ttlTTL in seconds

Best Practices

Use CNAMEs for Flexibility

Prefer CNAME records when possible:

dns:
  # Single A record
  - name: ingress
    type: A
    target: "{{ .Resources.clusters.main.ingress_ip }}"

  # Multiple CNAMEs pointing to it
  - name: app
    type: CNAME
    target: ingress

  - name: api
    type: CNAME
    target: ingress

  - name: admin
    type: CNAME
    target: ingress

If the IP changes, you only update one record.

Wildcard for Ingress

Use wildcards for Kubernetes ingress:

dns:
  - name: "*"
    type: A
    target: "{{ .Resources.clusters.main.ingress_ip }}"

This allows any ingress hostname to work without additional DNS configuration.

Document Your DNS

Use consistent naming conventions:

dns:
  # Primary services
  - name: app           # Main application
  - name: api           # API endpoint
  - name: admin         # Admin interface

  # Infrastructure
  - name: gitlab        # Git/CI
  - name: grafana       # Monitoring
  - name: argocd        # GitOps

  # Participant access
  - name: "p{{ .Index }}"  # Participant workstations

Related Resources

ende