7.9 KiB
Service Registrations
Wild Central provides DNS, proxy routing, TLS, and DDNS for services on the LAN. External consumers (Wild Cloud, Wild Works) register their domains with Central's API, and Central handles the networking.
Registration API
POST /api/v1/services Register a domain
GET /api/v1/services List all registrations
GET /api/v1/services/{domain} Get registration details
PATCH /api/v1/services/{domain} Update a registration
DELETE /api/v1/services/{domain} Remove a registration
DELETE /api/v1/services/deregister?source=X&backend=Y Batch cleanup
The domain is the unique key. One registration per domain.
Fields
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
domain |
string | yes | — | FQDN to route. Unique key. |
source |
string | no | "manual" |
Who registered this: wild-cloud, wild-works, manual. |
backend.address |
string | yes* | — | Target host:port (e.g., 192.168.8.240:80). |
backend.type |
string | yes* | — | tcp-passthrough or http. |
backend.health |
string | no | — | Health check path for L7 services (e.g., /health). |
subdomains |
bool | no | false |
If true, also routes *.domain traffic. |
public |
bool | no | false |
If true, domain gets public DNS + internet exposure. |
tls |
string | no | inferred | passthrough or terminate. Defaults based on backend type. |
routes |
array | no* | — | Path-based multi-backend routing (see below). |
*A service must have either backend (simple) or routes (multi-backend), not both.
Simple vs multi-backend services
Simple (single backend)
Most services use a single backend. All traffic for the domain goes to one place:
{
"domain": "my-api.payne.io",
"backend": {"address": "192.168.8.60:9001", "type": "http"},
"tls": "terminate"
}
Multi-backend (path-based routing)
When different URL paths on the same domain need to reach different backends, use routes. Each route maps path prefixes to a backend with its own L7 options. Routes are evaluated in order — first match wins. A route with empty paths is a catch-all (should be last).
{
"domain": "payne.io",
"public": true,
"tls": "terminate",
"routes": [
{
"paths": ["/_matrix", "/.well-known/matrix"],
"backend": {"address": "192.168.8.60:8008", "type": "http", "health": "/health"}
},
{
"backend": {"address": "192.168.8.240:80", "type": "http"}
}
]
}
Here /_matrix and /.well-known/matrix go to a Matrix server, everything else goes to a website. Both backends can be on different hosts.
Route fields
Each route in the routes array supports:
| Field | Type | Required | Description |
|---|---|---|---|
paths |
[]string | no | Path prefixes to match (empty = catch-all). |
backend.address |
string | yes | Target host:port. |
backend.type |
string | yes | http (routes only support L7). |
backend.health |
string | no | Health check path. |
headers.request |
map | no | Request headers to set (e.g., X-Forwarded-Proto: https). |
headers.response |
map | no | Response headers to set (e.g., CORS headers). |
ipAllow |
[]string | no | CIDR whitelist — deny requests not matching (e.g., ["10.0.0.0/8"]). |
User-facing concepts
The API fields map to three user-visible controls:
Public / Private
public: true or public: false
- Private (default): Domain resolves only on the LAN. No public DNS record. Accessible only from your local network or VPN.
- Public: Domain resolves on the LAN AND has a public DNS A record. Accessible from the internet through Central's HAProxy.
Subdomains
subdomains: true or subdomains: false
- Off (default): Only the exact domain is routed (
payne.ioroutes, butfoo.payne.iodoes not). - On: Both the domain and all subdomains are routed (
cloud.payne.ioANDmatrix.cloud.payne.io,vaultwarden.cloud.payne.io, etc.).
TLS handling
tls: "passthrough" or tls: "terminate"
- Passthrough: Central forwards encrypted traffic directly to the backend. The backend handles its own TLS certificates. Maps to
backend.type: "tcp-passthrough". - Terminate: Central provisions a TLS certificate and handles HTTPS. Traffic is decrypted at Central and proxied as HTTP to the backend. Maps to
backend.type: "http".
What Central does per registration
When a service is registered, Central automatically:
| Effect | Passthrough | Terminate |
|---|---|---|
| LAN DNS | host-record=<domain>,<backend-IP> (exact) or address=/<domain>/<backend-IP> (wildcard if subdomains:true) |
host-record=<domain>,<central-IP> (exact) or address=/<domain>/<central-IP> (wildcard if subdomains:true) |
| AAAA filtering | filter-AAAA globally strips IPv6 from upstream responses (prevents Happy Eyeballs on IPv4-only LAN) |
Same |
| Public DNS | DDNS A record if public | Same |
| Proxy | L4 SNI passthrough. Subdomains adds *.domain matching. |
L7 HTTP reverse proxy by Host header. Routes add path-based ACLs. |
| TLS | Backend handles — Central passes through | Central provisions cert via Let's Encrypt |
Examples
Wild Cloud k8s cluster (terminate, public, wildcard)
Wild Cloud registers its instances as HTTP terminate services. Central handles TLS and forwards plain HTTP to the cluster's Traefik ingress controller.
{
"domain": "cloud.payne.io",
"source": "wild-cloud",
"backend": {"address": "192.168.8.240:80", "type": "http"},
"subdomains": true,
"public": true
}
Central creates: LAN DNS → Central IP, public DNS A record, HAProxy L7 TLS terminate + wildcard *.cloud.payne.io, cert via certbot.
Internal web app (terminate, private)
{
"domain": "wild-cloud.payne.io",
"source": "wild-cloud",
"backend": {"address": "127.0.0.1:5055", "type": "http"}
}
Central creates: LAN DNS → Central IP (host-record=, exact match), HAProxy L7 reverse proxy, TLS cert via certbot. No public DNS.
Service with custom response headers
{
"domain": "keila.cloud.payne.io",
"source": "wild-cloud",
"routes": [{
"backend": {"address": "192.168.8.240:80", "type": "http"},
"headers": {
"response": {
"Access-Control-Allow-Origin": "https://cloud.payne.io"
}
}
}],
"public": true
}
Service with IP whitelisting (internal only)
{
"domain": "headlamp.internal.cloud.payne.io",
"source": "wild-cloud",
"routes": [{
"backend": {"address": "192.168.8.240:80", "type": "http"},
"ipAllow": ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"]
}]
}
L4 passthrough (non-HTTP services)
For services that handle their own TLS (e.g., standalone apps with built-in cert management):
{
"domain": "payne.io",
"source": "wild-cloud",
"backend": {"address": "192.168.8.240:443", "type": "tcp-passthrough"},
"public": true
}
Central creates: LAN DNS → backend IP, public DNS A record, HAProxy L4 SNI passthrough. TLS handled by backend.
Wild Cloud integration
Wild Cloud instances with central.registration: per-app in their instance config use a client-go Ingress watcher that automatically registers domains with Central:
- One wildcard registration per instance domain (e.g.,
*.cloud.payne.iowithsubdomains: true) - One wildcard registration per internal domain (e.g.,
*.internal.cloud.payne.io) - Individual registrations for apps with special routing needs (custom headers, IP whitelisting, extra domains)
- Apps define special routing via a
centralsection in their manifest config
What is NOT a registration
Central's own services come from config, not the registration API:
- Central's UI domain (
cloud.central.domain) - VPN (
cloud.vpn.*) - Firewall rules (
cloud.nftables.*) - DHCP (
cloud.dnsmasq.dhcp.*) - CrowdSec, certbot credentials, DDNS provider config