Vardo

Domains & TLS

Custom domains, automatic TLS via Let's Encrypt, DNS configuration, and domain monitoring.

Vardo uses Traefik as its reverse proxy. Traefik discovers containers via Docker labels and handles TLS certificate provisioning automatically.

How it works

When you add a domain to an app, Vardo injects Traefik labels into the compose file at deploy time. Traefik reads those labels from running containers on the vardo-network Docker network and starts routing traffic — no config reload needed.

Adding a custom domain

Go to your app -> Domains -> Add Domain.

Enter the domain (e.g. app.example.com).

Configure DNS (see below).

Deploy the app. Traefik requests a TLS certificate automatically.

Each domain is associated with one app and one container port. Multiple domains can point to the same app.

Domain fields

FieldDescription
domainFully qualified domain name
portContainer port for this domain (overrides app default)
certResolverCertificate resolver to use (default: le)
sslEnabledEnable HTTPS (default: true)
isPrimaryMark as the primary domain
serviceNameTarget a specific service in a multi-service compose file

DNS configuration

Point your domain at the server running Vardo.

A record (recommended for root domains):

app.example.com.  A  <your-server-ip>

CNAME record (for subdomains):

app.example.com.  CNAME  your-vardo-host.example.com.

DNS changes typically propagate in minutes but can take up to 48 hours. Traefik won't issue a certificate until DNS resolves to the server.

Wildcard domains

Wildcard domains (e.g. *.example.com) require a DNS challenge for certificate issuance. Configure the DNS provider credentials in your Traefik configuration.

Wildcard certificate support via DNS challenge requires additional Traefik configuration not managed by Vardo directly.

Automatic TLS

When sslEnabled: true (the default), Vardo injects these Traefik labels:

traefik.http.routers.{routerName}.entrypoints: websecure
traefik.http.routers.{routerName}.tls: "true"
traefik.http.routers.{routerName}.tls.certresolver: le
# HTTP -> HTTPS redirect
traefik.http.routers.{routerName}-http.entrypoints: web
traefik.http.routers.{routerName}-http.middlewares: {routerName}-https-redirect
traefik.http.middlewares.{routerName}-https-redirect.redirectscheme.scheme: https
traefik.http.middlewares.{routerName}-https-redirect.redirectscheme.permanent: "true"

Traefik requests a certificate using the HTTP-01 challenge on port 80. Certificates are stored in Traefik's ACME storage and renewed automatically before expiry.

Multiple ACME issuers

Vardo supports three certificate issuers out of the box:

IssuerResolver keyNotes
Let's EncryptleDefault. Free, widely trusted.
Google Trust ServicesgoogleGoogle's public ACME CA.
ZeroSSLzerosslRequires External Account Binding (EAB) credentials.

Configuring the default issuer

Set the default in vardo.yml:

ssl:
  defaultIssuer: le  # le | google | zerossl

For ZeroSSL, add EAB credentials in vardo.secrets.yml:

zerossl:
  eabKid: "<your EAB KID>"
  eabHmac: "<your EAB HMAC key>"

You can also configure these via the admin UI under Settings -> SSL.

Per-domain cert resolver

Override the default issuer on any individual domain by setting the certResolver field in domain settings. This lets you use Let's Encrypt for most domains but ZeroSSL or Google for specific ones.

HTTP-only mode

To disable TLS for a domain, set sslEnabled: false. Vardo routes the domain on the web entrypoint without TLS or redirect.

.localhost domains for development

Domains ending in .localhost get special treatment:

  • TLS is configured but no cert resolver is set — Traefik generates a self-signed certificate
  • No DNS needed — *.localhost resolves to 127.0.0.1 on most systems

Example: myapp.localhost works out of the box for local development.

Auto-generated subdomains

When you create an app, Vardo can generate a subdomain automatically:

TypePatternExample
App{appName}-{adjective}-{noun}.{baseDomain}myapp-spicy-mango.example.com
Environment{appName}-{envName}.{baseDomain}myapp-staging.example.com
Preview{appName}-pr-{prNumber}.{baseDomain}myapp-pr-42.example.com

The base domain is configured per-organization (org.baseDomain), with a fallback to VARDO_BASE_DOMAIN from the environment.

Domain health checks

Vardo monitors all domains attached to active apps every 5 minutes:

  1. Probes each domain via HTTPS with a 10-second timeout
  2. Falls back to HTTP if HTTPS fails
  3. Records a domain_check entry: reachable status, HTTP status code, response time
  4. Keeps the last 100 checks per domain (older entries are pruned)

A domain is considered reachable if the HTTP status code is < 500.

State transition alerts

When a domain transitions from reachable to unreachable, Vardo logs a warning:

[domain-monitor] WARNING: app.example.com (app: myapp) is unreachable — connect ECONNREFUSED

In the UI, each domain shows its last check status and response time. The API exposes check history per domain.

Traefik network

All Vardo-managed containers attach to the vardo-network Docker bridge network. Traefik must be on this network to discover containers.

networks:
  vardo-network:
    external: true

Vardo creates the network automatically if it doesn't exist during deploy.

Cloudflare configuration

If you're using Cloudflare as your DNS provider:

DNS settings:

  • Set records to DNS only (grey cloud) while setting up TLS. Switch to proxied once Let's Encrypt certificates are working.
  • For Full (Strict) SSL mode, Cloudflare requires a valid origin certificate. Let's Encrypt satisfies this.

SSL/TLS settings:

  • Mode: Full (Strict)
  • Always use HTTPS: enabled
  • Automatic HTTPS Rewrites: enabled

With Cloudflare's proxy (orange cloud), HTTP-01 ACME challenges still work — but ensure port 80 is accessible from Cloudflare's servers.

Troubleshooting

API

GET    /api/v1/organizations/{orgId}/domains
POST   /api/v1/organizations/{orgId}/apps/{appId}/domains
GET    /api/v1/organizations/{orgId}/apps/{appId}/domains
PUT    /api/v1/organizations/{orgId}/apps/{appId}/domains/{domainId}
DELETE /api/v1/organizations/{orgId}/apps/{appId}/domains/{domainId}

On this page