diff --git a/DNS.md b/DNS.md
new file mode 100644
index 0000000..b41334a
--- /dev/null
+++ b/DNS.md
@@ -0,0 +1,99 @@
+# DNS Management - gigafibre.ca
+
+## Provider
+
+- **Registrar**: OpenSRS (manage.opensrs.com)
+- **Account**: targo (louispaul@targointernet.com)
+- **API endpoint**: https://rr-n1-tor.opensrs.net:55443 (XCP/XML)
+- **Domain**: gigafibre.ca
+- **Server IP**: 96.125.196.67
+
+## Managing DNS Records
+
+### Option 1: Traefik Hub UI (recommended)
+
+1. Go to https://hub.gigafibre.ca (or http://96.125.196.67:3080)
+2. Login: admin / targo2026
+3. Click **DNS Records** in sidebar
+4. Add/delete A records directly
+
+The Hub calls the OpenSRS API internally. No additional tools needed.
+
+### Option 2: Quick Deploy (DNS + Route + SSL in one step)
+
+1. In Traefik Hub, click **Deploy Service**
+2. Fill in image, subdomain, port
+3. Click **Deploy**
+4. DNS record + Traefik route + SSL certificate created automatically
+
+### Option 3: CLI via curl (OpenSRS XCP API)
+
+The OpenSRS API uses XML over HTTPS with HMAC-MD5 signature.
+
+**Authentication:**
+- Header `X-Username`: reseller username
+- Header `X-Signature`: MD5(MD5(xml + api_key) + api_key)
+- IP whitelist required in manage.opensrs.com > Account Settings > API Settings
+
+**Get current records:**
+
+```bash
+API_KEY="your-api-key"
+XML='- XCP
- GET_DNS_ZONE
- DOMAIN
- gigafibre.ca
'
+SIG=$(echo -n "$(echo -n "${XML}${API_KEY}" | md5sum | cut -d' ' -f1)${API_KEY}" | md5sum | cut -d' ' -f1)
+curl -s -4 -X POST "https://rr-n1-tor.opensrs.net:55443" \
+ -H "Content-Type: text/xml" -H "X-Username: targo" -H "X-Signature: ${SIG}" \
+ -d "${XML}"
+```
+
+**Set records (replaces ALL A records):**
+
+```bash
+XML='- XCP
- SET_DNS_ZONE
- DOMAIN
- gigafibre.ca
- 96.125.196.67
- www
- 96.125.196.67
- oss
- 96.125.196.67
'
+```
+
+**IMPORTANT**: SET_DNS_ZONE replaces the entire zone. Always GET first, modify, then SET.
+
+## Current A Records
+
+| Subdomain | IP | Service |
+|-----------|-----|---------|
+| @ (root) | 96.125.196.67 | Default |
+| www | 96.125.196.67 | Default |
+| oss | 96.125.196.67 | Oktopus |
+| git | 96.125.196.67 | Gitea |
+| dispatch | 96.125.196.67 | Dispatch App |
+| hub | 96.125.196.67 | Traefik Hub |
+| traefik | 96.125.196.67 | Traefik Dashboard |
+| erp | 96.125.196.67 | ERPNext (future) |
+| monitor | 96.125.196.67 | Device Monitor (future) |
+| timesheet | 96.125.196.67 | Targo Timesheet (disabled) |
+
+## Adding a new subdomain (full workflow)
+
+1. **Add DNS** (Hub UI > DNS Records > Add, or CLI)
+2. **Add Traefik route** (Hub UI > Routes > Add, or docker labels)
+3. **Wait 1-5 min** for DNS propagation
+4. **SSL auto-generated** by Traefik Let's Encrypt on first HTTPS request
+
+## Traefik Hub internals
+
+The Hub manages DNS via these components:
+- **Backend**: Node.js + dockerode (server.js)
+- **OpenSRS client**: Built-in, uses HTTPS + XML + MD5 signature
+- **Config**: Environment variables in traefik-hub/docker-compose.yml
+ - `OPENSRS_USER=targo`
+ - `OPENSRS_KEY=`
+ - `OPENSRS_DOMAIN=gigafibre.ca`
+ - `SERVER_IP=96.125.196.67`
+- **Routes file**: /opt/traefik/dynamic/routes.yml (Traefik file provider, watched live)
+- **Docker socket**: /var/run/docker.sock (container management)
+
+## OpenSRS API gotchas
+
+1. **IP whitelist required** - Add your public IP in manage.opensrs.com > Account Settings > API Settings
+2. **SET replaces everything** - Always GET first, modify the array, then SET
+3. **XML format only** - No JSON API available
+4. **Signature is double-MD5** - MD5(MD5(body + key) + key)
+5. **Propagation**: 1-15 minutes for new records
+6. **Rate limits**: Not documented but ~60 requests/minute is safe