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

# 3. Configure a Custom Domain

> Map your own domain to a workload with automatic TLS certificates, path-based routing, and geo-routing.

## Overview

This quickstart guides you through configuring a custom domain for your workload. You'll map your own domain to your application with automatic TLS certificates, load balancing, and geo-routing.

**What you'll accomplish:**

* Register and verify your apex domain
* Configure path-based routing to your workload
* Access your application via your custom domain

## Prerequisites

* Completed [Quickstart 1](/quickstart/quick-start-1-deploy-workload) with a running [workload](/guides/create-workload)
* A domain name you own with access to its DNS settings

<Note>
  This quickstart uses `example.com` as an example domain. Replace it with your own domain throughout the guide.
</Note>

So far, you've created a GVC (`quickstart-gvc`) and deployed a workload (`hello-world`). Now you'll configure a custom domain that routes traffic to your application.

<Tabs>
  <Tab title="Console UI">
    ## Step 1: Register Your Apex Domain

    An apex domain (e.g., `example.com`) must be verified before creating subdomains. Even if you only use subdomains, verify the apex first.

    <Steps>
      <Step title="Create the apex domain">
        Click `Domains` in the left menu, then click `New`. Click `Advanced`, then enter your apex domain (e.g., `example.com`).
      </Step>

      <Step title="Verify domain ownership">
        You'll be prompted to verify ownership by adding a TXT record to your DNS. Add the displayed TXT record to your DNS provider and wait a few minutes for propagation.
      </Step>

      <Step title="Set routing mode to None">
        Scroll down to `Routing Mode` and select `None` because you're only verifying ownership of the apex domain.
      </Step>

      <Step title="Create the apex domain">
        Click `Create`.
      </Step>
    </Steps>

    <Tip>Create the apex domain in your production org as a best practice.</Tip>

    ## Step 2: Configure Your Subdomain

    Now configure a subdomain (e.g., `app.example.com`) to route to your workload.

    <Steps>
      <Step title="Create the subdomain">
        Click `Domains` > `New`. Enter your subdomain (e.g., `app.example.com`).
      </Step>

      <Step title="Select workload">
        Select the `hello-world` workload from the `quickstart-gvc` GVC.
      </Step>

      <Step title="Configure DNS">
        Add the displayed CNAME record to your DNS provider.
      </Step>

      <Step title="Create the subdomain">
        Once the DNS record is configured, click `Create`.
      </Step>
    </Steps>

    <Note>
      Control Plane automatically provisions TLS certificates after the DNS propagates and your workload is ready. This may take a few minutes.
    </Note>

    ## Step 3: Test Your Domain

    Open `https://app.example.com` in your browser. Your application loads with a valid TLS certificate.
  </Tab>

  <Tab title="CLI">
    ## Step 1: Register Your Apex Domain

    First, attempt to create the apex domain:

    ```bash theme={null}
    cpln domain create --name example.com
    ```

    The command returns an error with the TXT record you need to add to verify ownership. Add one of the displayed TXT records to your DNS provider and wait a few minutes for propagation, then run the command again:

    ```bash theme={null}
    cpln domain create --name example.com
    ```

    Once ownership is verified, the domain is created successfully.

    ## Step 2: Create Domain Configuration

    Create a YAML file `domain.yaml` with your domain configuration:

    ```yaml theme={null}
    kind: domain
    name: app.example.com
    spec:
      dnsMode: cname
      ports:
        - number: 443
          protocol: http2
          routes:
            - prefix: /
              workloadLink: //gvc/quickstart-gvc/workload/hello-world
    ```

    Apply the configuration:

    ```bash theme={null}
    cpln apply -f domain.yaml
    ```

    ## Step 3: Configure DNS

    Get the GVC alias:

    ```bash theme={null}
    cpln gvc get quickstart-gvc -o yaml
    ```

    Find the `alias` field in the output:

    ```yaml theme={null}
    alias: abc123xyz
    ```

    Add a CNAME record to your DNS provider with the following values:

    | Type  | Host | Value                     | TTL |
    | ----- | ---- | ------------------------- | --- |
    | CNAME | app  | `<your-alias>.t.cpln.app` | 300 |

    Replace `<your-alias>` with the alias value from the output above.

    ## Step 4: Verify

    After DNS propagates, access your application at `https://app.example.com`.
  </Tab>

  <Tab title="Terraform">
    ## Step 1: Define the Apex Domain

    Add the following to your `main.tf` to verify domain ownership:

    ```hcl theme={null}
    resource "cpln_domain" "apex" {
      name        = "example.com"
      description = "Apex domain for verification"

      spec {
        dns_mode = "cname"

        ports {
          number   = 443
          protocol = "http2"
          
          tls {}
        }
      }
    }
    ```

    Apply the configuration to retrieve the TXT records you need to add to your DNS:

    ```bash theme={null}
    terraform apply
    ```

    The output contains the TXT record you need to add to your DNS provider to prove ownership. Add the TXT record and wait a few minutes for propagation, then run `terraform apply` again to create the domain.

    ## Step 2: Define the Subdomain and Route

    Add the subdomain and routing configuration:

    ```hcl theme={null}
    resource "cpln_domain" "app" {
      depends_on  = [cpln_domain.apex]
      name        = "app.example.com"
      description = "Application domain"

      spec {
        dns_mode = "cname"

        ports {
          number   = 443
          protocol = "http2"

          tls {}
        }
      }
    }

    resource "cpln_domain_route" "route" {
      depends_on = [cpln_domain.app]

      domain_link   = cpln_domain.app.self_link
      domain_port   = 443
      prefix        = "/"
      workload_link = cpln_workload.hello_world.self_link
    }

    output "domain_endpoint" {
      value = "https://app.example.com"
    }

    output "dns_cname_record" {
      value = "Type: CNAME | Host: app | Value: ${cpln_gvc.quickstart.alias}.t.cpln.app | TTL: 300"
    }
    ```

    ## Step 3: Apply and Configure DNS

    ```bash theme={null}
    terraform apply
    ```

    The `dns_cname_record` output shows the CNAME record you need to add to your DNS provider. The value will be in the format `<gvcAlias>.t.cpln.app`.

    ## Step 4: Verify

    After DNS propagates, access your application at `https://app.example.com`.

    <Tip>
      View the full domain resource options in the [Terraform Registry documentation](https://registry.terraform.io/providers/controlplane-com/cpln/latest/docs/resources/domain).
    </Tip>
  </Tab>

  <Tab title="Pulumi">
    ## Step 1: Define the Apex Domain

    <Tabs>
      <Tab title="TypeScript">
        Add to your `index.ts`:

        ```typescript theme={null}
        // Verify apex domain ownership
        const apexDomain = new cpln.Domain("apex-domain", {
          name: "example.com",
          description: "Apex domain for verification",
          spec: {
            dnsMode: "cname",
            ports: [
              {
                number: 443,
                protocol: "http2",
                tls: {},
              },
            ],
          },
        });
        ```
      </Tab>

      <Tab title="Python">
        Add to your `__main__.py`:

        ```python theme={null}
        # Verify apex domain ownership
        apex_domain = cpln.Domain("apex-domain",
            name="example.com",
            description="Apex domain for verification",
            spec=cpln.DomainSpecArgs(
                dns_mode="cname",
                ports=[cpln.DomainSpecPortArgs(
                    number=443,
                    protocol="http2",
                    tls=cpln.DomainSpecPortTlsArgs(),
                )],
            ))
        ```
      </Tab>

      <Tab title="Go">
        Add to your `main.go`:

        ```go theme={null}
        // Verify apex domain ownership
        apexDomain, err := cpln.NewDomain(ctx, "apex-domain", &cpln.DomainArgs{
        	Name:        pulumi.String("example.com"),
        	Description: pulumi.String("Apex domain for verification"),
        	Spec: &cpln.DomainSpecArgs{
        		DnsMode: pulumi.String("cname"),
        		Ports: cpln.DomainSpecPortArray{
        			&cpln.DomainSpecPortArgs{
        				Number:   pulumi.Int(443),
        				Protocol: pulumi.String("http2"),
        				Tls:      &cpln.DomainSpecPortTlsArgs{},
        			},
        		},
        	},
        })
        if err != nil {
        	return err
        }
        _ = apexDomain // Used in Step 2
        ```
      </Tab>

      <Tab title="C#">
        Add to your `Program.cs`:

        ```csharp theme={null}
        // Verify apex domain ownership
        var apexDomain = new Domain("apex-domain", new DomainArgs
        {
            Name = "example.com",
            Description = "Apex domain for verification",
            Spec = new DomainSpecArgs
            {
                DnsMode = "cname",
                Ports = new[]
                {
                    new DomainSpecPortArgs
                    {
                        Number = 443,
                        Protocol = "http2",
                        Tls = new DomainSpecPortTlsArgs {}
                    }
                }
            }
        });
        ```
      </Tab>
    </Tabs>

    Deploy the configuration to retrieve the TXT records you need to add to your DNS:

    ```bash theme={null}
    pulumi up
    ```

    The output contains the TXT record you need to add to your DNS provider to prove ownership. Add the TXT record and wait a few minutes for propagation, then run `pulumi up` again to create the domain.

    ## Step 2: Define the Subdomain and Route

    <Tabs>
      <Tab title="TypeScript">
        ```typescript theme={null}
        // Configure subdomain
        const appDomain = new cpln.Domain("app-domain", {
          name: "app.example.com",
          description: "Application domain",
          spec: {
            dnsMode: "cname",
            ports: [
              {
                number: 443,
                protocol: "http2",
                tls: {},
              },
            ],
          },
        }, { dependsOn: [apexDomain] });

        // Configure route
        const route = new cpln.DomainRoute("route", {
          domainLink: appDomain.selfLink,
          domainPort: 443,
          prefix: "/",
          workloadLink: workload.selfLink,
        }, { dependsOn: [appDomain] });

        export const domain_endpoint = "https://app.example.com";
        export const dns_cname_record = gvc.alias.apply(
          alias => `Type: CNAME | Host: app | Value: ${alias}.t.cpln.app | TTL: 300`
        );
        ```
      </Tab>

      <Tab title="Python">
        ```python theme={null}
        # Configure subdomain
        app_domain = cpln.Domain("app-domain",
            name="app.example.com",
            description="Application domain",
            spec=cpln.DomainSpecArgs(
                dns_mode="cname",
                ports=[cpln.DomainSpecPortArgs(
                    number=443,
                    protocol="http2",
                    tls=cpln.DomainSpecPortTlsArgs(),
                )],
            ),
            opts=pulumi.ResourceOptions(depends_on=[apex_domain]))

        # Configure route
        route = cpln.DomainRoute("route",
            domain_link=app_domain.self_link,
            domain_port=443,
            prefix="/",
            workload_link=workload.self_link,
            opts=pulumi.ResourceOptions(depends_on=[app_domain]))

        pulumi.export("domain_endpoint", "https://app.example.com")
        pulumi.export("dns_cname_record", gvc.alias.apply(
            lambda alias: f"Type: CNAME | Host: app | Value: {alias}.t.cpln.app | TTL: 300"
        ))
        ```
      </Tab>

      <Tab title="Go">
        ```go theme={null}
        // Configure subdomain
        appDomain, err := cpln.NewDomain(ctx, "app-domain", &cpln.DomainArgs{
        	Name:        pulumi.String("app.example.com"),
        	Description: pulumi.String("Application domain"),
        	Spec: &cpln.DomainSpecArgs{
        		DnsMode: pulumi.String("cname"),
        		Ports: cpln.DomainSpecPortArray{
        			&cpln.DomainSpecPortArgs{
        				Number:   pulumi.Int(443),
        				Protocol: pulumi.String("http2"),
        				Tls:      &cpln.DomainSpecPortTlsArgs{},
        			},
        		},
        	},
        }, pulumi.DependsOn([]pulumi.Resource{apexDomain}))
        if err != nil {
        	return err
        }

        // Configure route
        _, err = cpln.NewDomainRoute(ctx, "route", &cpln.DomainRouteArgs{
        	DomainLink:   appDomain.SelfLink,
        	DomainPort:   pulumi.Int(443),
        	Prefix:       pulumi.String("/"),
        	WorkloadLink: workload.SelfLink,
        }, pulumi.DependsOn([]pulumi.Resource{appDomain}))
        if err != nil {
        	return err
        }

        ctx.Export("domain_endpoint", pulumi.String("https://app.example.com"))
        ctx.Export("dns_cname_record", gvc.Alias.ApplyT(func(alias string) string {
        	return fmt.Sprintf("Type: CNAME | Host: app | Value: %s.t.cpln.app | TTL: 300", alias)
        }).(pulumi.StringOutput))
        ```
      </Tab>

      <Tab title="C#">
        ```csharp theme={null}
        // Configure subdomain
        var appDomain = new Domain("app-domain", new DomainArgs
        {
            Name = "app.example.com",
            Description = "Application domain",
            Spec = new DomainSpecArgs
            {
                DnsMode = "cname",
                Ports = new[]
                {
                    new DomainSpecPortArgs
                    {
                        Number = 443,
                        Protocol = "http2",
                        Tls = new DomainSpecPortTlsArgs {}
                    }
                }
            }
        }, new CustomResourceOptions { DependsOn = { apexDomain } });

        // Configure route
        var route = new DomainRoute("route", new DomainRouteArgs
        {
            DomainLink = appDomain.SelfLink,
            DomainPort = 443,
            Prefix = "/",
            WorkloadLink = workload.SelfLink
        }, new CustomResourceOptions { DependsOn = { appDomain } });
        ```

        Update your return dictionary to include the new outputs:

        ```csharp theme={null}
        return new Dictionary<string, object?>
        {
            ["canonical_endpoint"] = workload.Statuses.Apply(s => s[0].CanonicalEndpoint),
            ["domain_endpoint"] = "https://app.example.com",
            ["dns_cname_record"] = gvc.Alias.Apply(alias =>
                $"Type: CNAME | Host: app | Value: {alias}.t.cpln.app | TTL: 300")
        };
        ```
      </Tab>
    </Tabs>

    ## Step 3: Deploy and Configure DNS

    ```bash theme={null}
    pulumi up
    ```

    The `dns_cname_record` output shows the CNAME record you need to add to your DNS provider. The value will be in the format `<gvcAlias>.t.cpln.app`.

    ## Step 4: Verify

    After DNS propagates, access your application at `https://app.example.com`.

    <Tip>
      View the full domain resource options in the [Pulumi Registry documentation](https://www.pulumi.com/registry/packages/cpln/api-docs/domain/).
    </Tip>
  </Tab>
</Tabs>

## Routing Modes

Control Plane supports two routing modes:

| Mode                | DNS Record | Best For                                               |
| ------------------- | ---------- | ------------------------------------------------------ |
| **Path-based**      | CNAME      | Multiple workloads on different paths (`/api`, `/web`) |
| **Subdomain-based** | NS         | Unique subdomain per workload (`api.app.example.com`)  |

<AccordionGroup>
  <Accordion title="Path-based routing examples">
    Route different paths to different workloads:

    * `https://app.example.com/api` → API workload
    * `https://app.example.com/web` → Frontend workload
    * `https://app.example.com/` → Default workload
  </Accordion>

  <Accordion title="Subdomain-based routing examples">
    Each workload automatically receives its own subdomain:

    * `https://api.app.example.com` → API workload
    * `https://web.app.example.com` → Frontend workload

    Requires NS record delegation to Control Plane.
  </Accordion>
</AccordionGroup>

## What's Configured

Your domain is now configured with:

* **Automatic TLS** - Certificates provisioned and renewed automatically
* **Global load balancing** - Traffic is routed to the nearest healthy location
* **Path-based routing** - Multiple workloads can share the same domain

## Continue

<Card title="4. Service-to-Service Communication" icon="arrows-left-right" href="/quickstart/quick-start-4-service-to-service" horizontal>
  Learn how workloads communicate internally with mTLS encryption.
</Card>

## Clean Up

If you want to stop here instead, delete the resources created in this quickstart:

<Tabs>
  <Tab title="Console UI">
    Navigate to `Domains`, select your domain, and click `Delete`.
  </Tab>

  <Tab title="CLI">
    ```bash theme={null}
    cpln domain delete app.example.com
    cpln domain delete example.com
    ```
  </Tab>

  <Tab title="Terraform">
    ```bash theme={null}
    terraform destroy
    ```
  </Tab>

  <Tab title="Pulumi">
    ```bash theme={null}
    pulumi destroy
    ```
  </Tab>
</Tabs>

<Note>
  Remember to remove the DNS records from your DNS provider after deleting the domain from Control Plane.
</Note>
