4.2 KiB
Adding a New Subdomain
Every new subdomain needs to be registered in three places. Miss one and either the DNS won't auto-update when your WAN IP changes, or the service won't be reachable.
The Three Places
| # | Where | What it does |
|---|---|---|
| 1 | Cloudflare DNS | Creates the A record |
| 2 | DDNS compose file | Keeps the A record pointed at your current WAN IP |
| 3 | NPM proxy host | Routes HTTPS traffic to the right container |
Step 1 — Cloudflare DNS
Create the A record via the Cloudflare dashboard or API.
Proxied (orange cloud) — use for all standard HTTP/HTTPS services:
curl -s -X POST "https://api.cloudflare.com/client/v4/zones/ZONE_ID/dns_records" \
-H "Authorization: Bearer $CF_TOKEN" \
-H "Content-Type: application/json" \
-d '{"type":"A","name":"myservice.vish.gg","content":"1.2.3.4","proxied":true}'
Direct (grey cloud) — use only for non-HTTP protocols (TURN, SSH, game servers, WebRTC):
# same but "proxied":false
Zone IDs:
| Domain | Zone ID |
|---|---|
vish.gg |
4dbd15d096d71101b7c0c6362b307a66 |
thevish.io |
11681f1c93ca32f56a0c41973e02b6f9 |
crista.love |
(check Cloudflare dashboard) |
The content IP doesn't matter much if it's proxied — the DDNS updater will overwrite it.
Use a placeholder like 1.2.3.4 for now.
Step 2 — DDNS Compose File
Add the domain to the correct host's DDNS DOMAINS= list. Pick the host whose
WAN IP the service is behind:
| Host | File | Use when |
|---|---|---|
| Atlantis / Calypso (home) | hosts/synology/atlantis/dynamicdnsupdater.yaml |
Service is behind home WAN IP |
| concord-nuc | hosts/physical/concord-nuc/dyndns_updater.yaml |
API/direct-access on concord-nuc |
| Seattle VPS | hosts/vms/seattle/ddns-updater.yaml |
Service is on the Seattle VPS |
| Guava (crista.love) | hosts/physical/guava/portainer_yaml/dynamic_dns.yaml |
crista.love subdomains |
For a standard proxied service on Atlantis/Calypso, edit dynamicdnsupdater.yaml
and append your domain to the ddns-vish-proxied service:
- DOMAINS=...,myservice.vish.gg # add here, keep comma-separated
- PROXIED=true
For an unproxied (direct) domain, use the ddns-thevish-unproxied service or
create a new service block with PROXIED=false.
Then redeploy the stack via Portainer (Atlantis, stack dyndns-updater-stack, ID 613):
# Portainer API — or just use the UI: Stacks → dyndns-updater-stack → Editor → Update
Step 3 — NPM Proxy Host
Add a proxy host at http://npm.vish.gg:81 (or http://192.168.0.250:81):
- Hosts → Proxy Hosts → Add Proxy Host
- Domain names:
myservice.vish.gg - Forward hostname/IP: container name or LAN IP of the service
- Forward port: the service's internal port
- SSL tab: Request a new Let's Encrypt cert, enable Force SSL
- (Optional) Advanced tab: add Authentik forward-auth snippet if SSO is needed
Exceptions — services that skip Step 3
If your subdomain doesn't need an NPM proxy rule (direct-access APIs, WebRTC,
services with their own proxy), add it to DDNS_ONLY_EXCEPTIONS in
.gitea/scripts/dns-audit.py so the daily audit doesn't flag it:
DDNS_ONLY_EXCEPTIONS = {
...
"myservice.vish.gg", # reason: direct access / own proxy
}
Step 4 — Verify
Run the DNS audit to confirm everything is wired up:
cd /home/homelab/organized/repos/homelab
CF_TOKEN=<your-cf-token> \
NPM_EMAIL=<npm-admin-email> \
NPM_PASSWORD="REDACTED_PASSWORD" \
python3 .gitea/scripts/dns-audit.py
The CF token is stored in Portainer as CLOUDFLARE_API_TOKEN on the DDNS stacks.
NPM credentials are stored as NPM_EMAIL / NPM_PASSWORD Gitea Actions secrets.
The audit also runs automatically every day at 08:00 UTC — check the Gitea Actions tab.
Expected output:
✅ All N DDNS domains OK, CF and DDNS are in sync
Commit the changes
git add hosts/synology/atlantis/dynamicdnsupdater.yaml # (whichever file you edited)
git commit -m "Add myservice.vish.gg subdomain"
git push
Portainer will pick up the DDNS change on the next git redeploy, or trigger it manually.