Skip to content

Custom Domain

CP users can attach their own domain name to a deployed service. Once configured, the domain serves the service over HTTPS with an automatically provisioned Let's Encrypt certificate.

How It Works

user domain (e.g. dashboard.example.com)
    │  CNAME (DNS only)

cpctl.app


public IP (nginx + Let's Encrypt cert, per hostname, auto-provisioned)
    │  proxy_pass via internal network

compute cluster
  Traefik (routes by Host header via Swarm service labels)


Docker Swarm service (e.g. cp-dashboard)

User Setup

Add a single DNS record at your domain registrar or DNS provider:

NameTypeTargetProxy
dashboardCNAMEcpctl.appDNS only (grey cloud)

No Cloudflare account required. Works with any DNS provider. The record must be DNS only — do not enable proxying.

API

Attach a domain

http
POST /v1/services/:service/domains
Authorization: Bearer <cp-api-token>
Content-Type: application/json

{
  "hostname": "dashboard.koraccel.kr"
}

What happens on the server:

  1. Verifies the CNAME resolves to cpctl.app
  2. SSHs into the VPS and writes an nginx site config for the hostname
  3. Runs certbot --nginx to issue a Let's Encrypt certificate
  4. Reloads nginx
  5. Updates the Traefik router label on the Swarm service to include the hostname

Response:

json
{
  "hostname": "dashboard.koraccel.kr",
  "status": "active",
  "service": "cp-dashboard",
  "created_at": "2026-07-04T10:52:36.153Z"
}

Skip DNS check (for testing):

json
{ "hostname": "dashboard.koraccel.kr", "skip_dns": true }

List domains

http
GET /v1/services/:service/domains
Authorization: Bearer <cp-api-token>
json
{
  "domains": [
    { "hostname": "cp-dashboard.computeportal.io", "status": "active", "created_at": null },
    { "hostname": "dashboard.koraccel.kr", "status": "active", "created_at": null }
  ]
}

Remove a domain

http
DELETE /v1/services/:service/domains/:hostname
Authorization: Bearer <cp-api-token>

Returns 204 No Content. The default <service>.cpctl.app domain cannot be removed.

What happens on the server:

  1. Removes the nginx site config from the VPS
  2. Deletes the Let's Encrypt certificate via certbot delete
  3. Reloads nginx
  4. Removes the hostname from the Traefik router label

CLI

bash
cpctl domain add <service> <domain>        # attach a domain
cpctl domain list <service>                # list attached domains
cpctl domain status <service> <domain>     # check DNS and TLS status
cpctl domain remove <service> <domain>     # detach a domain

domain add

Before running this, add a CNAME record at your DNS provider:

NameTypeTargetProxy
dashboardCNAMEcpctl.appDNS only (grey cloud)

Then attach the domain:

bash
cpctl domain add my-service dashboard.example.com
✓ Domain dashboard.example.com attached to my-service

domain   dashboard.example.com
service  my-service
status   active
tls      pending

TLS is provisioned automatically via Let's Encrypt within a minute or two.

If DNS hasn't propagated yet, use --skip-dns to bypass the CNAME check:

bash
cpctl domain add my-service dashboard.example.com --skip-dns

domain list

bash
cpctl domain list my-service
HOSTNAME                          STATUS
my-service.cpctl.app          active
dashboard.example.com             active

domain status

bash
cpctl domain status my-service dashboard.example.com
domain          dashboard.example.com
status          active
tls             yes
dns_configured  yes
cname_target    cpctl.app
created_at      2026-07-01T10:00:00Z

domain remove

bash
cpctl domain remove my-service dashboard.example.com

The default <service>.cpctl.app domain cannot be removed.

MCP Tools

ToolDescription
cp_domain_addAttach a custom hostname to a service
cp_domain_listList all hostnames attached to a service
cp_domain_removeDetach a custom hostname from a service

Infrastructure

ComponentValue
CNAME targetcpctl.app
Edge proxyPublic IP running nginx + certbot
Public IP → clusterInternal private network
TLSLet's Encrypt via certbot HTTP-01, issued per hostname
RoutingTraefik Host-header rules on Docker Swarm service labels

Design Decisions

Why a VPS instead of Cloudflare Tunnel for the entry point? Cloudflare Tunnel records cannot be set to DNS-only while still resolving to an IP. Cross-account proxied CNAMEs trigger Cloudflare Error 1014. Using a VPS with a stable public IP removes any Cloudflare dependency for user domains — users can use any DNS provider and any proxy setting.

Why certbot per hostname instead of a wildcard cert? User domains are arbitrary (any domain, any registrar). A wildcard cert only covers *.cpctl.app. certbot with the nginx plugin issues and renews individual certs automatically.

Why not Cloudflare for SaaS? Requires a paid Cloudflare plan and assumes users stay on Cloudflare. The VPS approach works for any user regardless of their DNS provider.

GPU Compute Platform