Overview
This quickstart guides you through configuring a custom domain for your workload. You’ll map your own domain to serve traffic 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 with a running workload
A domain name you own with access to its DNS settings
This quickstart uses example.com as a sample domain. Replace it with your own domain throughout the guide.
So far, you’ve created a GVC (quickstart-gvc) and deployed a workload (hello-world). Now you’ll configure a custom domain to serve traffic to your application.
Console UI
CLI
Terraform
Pulumi
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.
Create the apex domain
Click Domains in the left menu, then click New. Click Advanced, then enter your apex domain (e.g., example.com).
Verify domain ownership
You’ll be prompted to prove 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.
Set routing mode to None
Scroll down to Routing Mode and select None since we’re only verifying ownership of the apex domain.
Create the apex domain
Click Create.
Create the apex domain in your production org as a best practice.
Step 2: Configure Your Subdomain Now configure a subdomain (e.g., app.example.com) to route to your workload.
Create the subdomain
Click Domains > New. Enter your subdomain (e.g., app.example.com).
Select workload
Select the hello-world workload from the quickstart-gvc GVC.
Configure DNS
Add the displayed CNAME record to your DNS provider.
Create the subdomain
Once the DNS record is configured, click Create.
Control Plane automatically provisions TLS certificates once DNS propagates and your workload is ready. This may take a few minutes.
Step 3: Test Your Domain Open https://app.example.com in your browser. Your application loads with a valid TLS certificate. Step 1: Register Your Apex Domain First, attempt to create the apex domain: cpln domain create --name example.com
The command returns an error with the TXT record you need to add to prove ownership. Add one of the displayed TXT records to your DNS provider and wait a few minutes for propagation, then run the command again: 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: 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: cpln apply -f domain.yaml
Get the GVC alias: cpln gvc get quickstart-gvc -o yaml
Look for the alias field in the output: Add a CNAME record to your DNS provider with the following values: Type Host Value TTL CNAME app <your-alias>.t.cpln.app300
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. Step 1: Define the Apex Domain Add to your main.tf to verify domain ownership: 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 see the TXT records you need to add to your DNS: 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 route configuration: 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"
}
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. Step 1: Define the Apex Domain Add to your index.ts: // 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: {},
},
],
},
});
Add to your __main__.py: # 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(),
)],
))
Add to your main.go: // 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
Add to your Program.cs: // 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 {}
}
}
}
});
Deploy the configuration to see the TXT records you need to add to your DNS: 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 // 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`
);
# 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"
))
// 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 ))
// 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: 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" )
};
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.
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)
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
Subdomain-based routing examples
Each workload gets its own subdomain automatically:
https://api.app.example.com → API workload
https://web.app.example.com → Frontend workload
Requires NS record delegation to Control Plane.
Your domain now provides:
Automatic TLS - Certificates provisioned and renewed automatically
Global load balancing - Traffic routed to the nearest healthy location
Path-based routing - Multiple workloads can share the same domain
Continue
4. Service-to-Service Communication Learn how workloads communicate internally with mTLS encryption.
Clean Up
If you want to stop here instead, delete the resources created in this quickstart:
Console UI
CLI
Terraform
Pulumi
Navigate to Domains, select your domain, and click Delete.
cpln domain delete app.example.com
cpln domain delete example.com
Remember to remove the DNS records from your DNS provider after deleting the domain from Control Plane.