hearth/docs/rfcs/0003-powerdns-self-hosted.md
Eric Garcia da40273177 feat(dns): Add self-hosted PowerDNS for 5 managed domains
- Deploy PowerDNS on k3s with SQLite backend
- Add DNS ports 53 UDP/TCP to security group
- Configure zones for superviber.com, muffinlabs.ai, letemcook.com,
  appbasecamp.com, thanksforborrowing.com
- Add deploy-powerdns.sh standalone deployment script
- Document in RFC 0003

Glue records updated at GoDaddy to point ns1/ns2 to 3.218.167.115.
DNS verified working via Google DNS (8.8.8.8).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 07:42:48 -05:00

340 lines
9.7 KiB
Markdown

# RFC 0003: PowerDNS Self-Hosted DNS
| | |
|---|---|
| **Status** | Implemented |
| **Created** | 2026-01-24 |
| **Related** | [coherence-mcp RFC 0046](../../coherence-mcp/docs/rfcs/0046-domain-email-migration.md) |
---
## Problem
We have 5 domains registered at GoDaddy that need DNS management:
- superviber.com
- muffinlabs.ai
- letemcook.com
- appbasecamp.com
- thanksforborrowing.com
Current state:
1. **Glue records exist** at GoDaddy pointing ns1/ns2.superviber.com to IPs 3.12.26.86 and 3.140.40.205
2. **No DNS server running** at those IPs (planned EKS infrastructure never deployed)
3. **Forgejo running** at 3.218.167.115 on k3s (hearth minimal), but DNS not configured
4. **Cloudflare partially configured** for superviber.com but nameservers not switched
We want self-hosted DNS for:
- Control over all records without third-party dashboard
- Integration with other self-hosted services (mail, git, auth)
- Single source of truth in infrastructure-as-code
## Goals
1. **Deploy PowerDNS** on existing k3s infrastructure (hearth)
2. **Configure zones** for all 5 managed domains
3. **Expose DNS** via port 53 UDP/TCP
4. **Update glue records** at GoDaddy to point to working DNS servers
5. **Add git.beyondtheuniverse.superviber.com** record pointing to Forgejo
## Non-Goals
- HA DNS with 3 separate IPs (future scope, current setup is single-user)
- DNSSEC (future scope)
- Secondary/slave DNS (future scope)
- DNS-over-HTTPS or DNS-over-TLS (future scope)
---
## Proposal
### Architecture
Deploy PowerDNS on the existing hearth k3s instance (3.218.167.115) as a single authoritative DNS server.
```
┌─────────────────────────────────────┐
│ EC2: 3.218.167.115 │
│ (hearth-forgejo) │
│ │
┌─────────────┐ │ ┌─────────────────────────────┐ │
│ GoDaddy │ │ │ k3s │ │
│ Glue: │ │ │ │ │
│ ns1/ns2 ────┼────────►│ │ ┌─────────┐ ┌──────────┐ │ │
│ .superviber │ :53 │ │ │PowerDNS │ │ Forgejo │ │ │
│ .com │ │ │ │ :53 │ │ :3000 │ │ │
└─────────────┘ │ │ └─────────┘ └──────────┘ │ │
│ │ │ │
│ │ Traefik (ingress) │ │
│ │ :80, :443, :22 │ │
│ └─────────────────────────────┘ │
└─────────────────────────────────────┘
```
### Why Single Instance
For a ~1 user personal infrastructure:
- Simpler ops (one machine to manage)
- Lower cost (~$7.50/month total)
- Acceptable availability (spot instance with persistent EIP)
- GoDaddy remains as backup (can revert NS records if needed)
### DNS Records
Initial zones will include:
**superviber.com:**
```
@ A 3.218.167.115
ns1 A 3.218.167.115
ns2 A 3.218.167.115
beyondtheuniverse A 3.218.167.115
git.beyondtheuniverse A 3.218.167.115
mail.beyondtheuniverse A 3.218.167.115
auth.beyondtheuniverse A 3.218.167.115
@ NS ns1.superviber.com.
@ NS ns2.superviber.com.
```
Other domains (muffinlabs.ai, etc.) will have minimal records pointing to the same infrastructure.
---
## Implementation
### Phase 1: Deploy PowerDNS on k3s
Add to hearth user-data.sh or apply directly:
```yaml
apiVersion: v1
kind: Namespace
metadata:
name: dns
---
apiVersion: v1
kind: Secret
metadata:
name: powerdns-api-key
namespace: dns
type: Opaque
stringData:
api-key: "GENERATED_SECRET_KEY"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: powerdns
namespace: dns
spec:
replicas: 1
selector:
matchLabels:
app: powerdns
template:
metadata:
labels:
app: powerdns
spec:
containers:
- name: powerdns
image: powerdns/pdns-auth-49:4.9.2
ports:
- name: dns-udp
containerPort: 53
protocol: UDP
- name: dns-tcp
containerPort: 53
protocol: TCP
- name: api
containerPort: 8081
protocol: TCP
env:
- name: PDNS_AUTH_API_KEY
valueFrom:
secretKeyRef:
name: powerdns-api-key
key: api-key
args:
- --api=yes
- --api-key=$(PDNS_AUTH_API_KEY)
- --webserver=yes
- --webserver-address=0.0.0.0
- --webserver-port=8081
- --launch=gsqlite3
- --gsqlite3-database=/var/lib/powerdns/pdns.sqlite3
volumeMounts:
- name: data
mountPath: /var/lib/powerdns
volumes:
- name: data
hostPath:
path: /data/powerdns
type: DirectoryOrCreate
---
apiVersion: v1
kind: Service
metadata:
name: powerdns
namespace: dns
spec:
type: LoadBalancer
externalTrafficPolicy: Local
selector:
app: powerdns
ports:
- name: dns-udp
port: 53
targetPort: 53
protocol: UDP
- name: dns-tcp
port: 53
targetPort: 53
protocol: TCP
- name: api
port: 8081
targetPort: 8081
protocol: TCP
```
### Phase 2: Update Security Group
Add to hearth main.tf security group:
```hcl
# DNS (UDP)
ingress {
description = "DNS UDP"
from_port = 53
to_port = 53
protocol = "udp"
cidr_blocks = ["0.0.0.0/0"]
}
# DNS (TCP)
ingress {
description = "DNS TCP"
from_port = 53
to_port = 53
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
```
### Phase 3: Configure Zones
Use PowerDNS API to create zones:
```bash
# Create zone
curl -X POST http://localhost:8081/api/v1/servers/localhost/zones \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "superviber.com.",
"kind": "Native",
"nameservers": ["ns1.superviber.com.", "ns2.superviber.com."]
}'
# Add records
curl -X PATCH http://localhost:8081/api/v1/servers/localhost/zones/superviber.com. \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"rrsets": [
{
"name": "superviber.com.",
"type": "A",
"ttl": 300,
"changetype": "REPLACE",
"records": [{"content": "3.218.167.115", "disabled": false}]
},
{
"name": "ns1.superviber.com.",
"type": "A",
"ttl": 3600,
"changetype": "REPLACE",
"records": [{"content": "3.218.167.115", "disabled": false}]
},
{
"name": "ns2.superviber.com.",
"type": "A",
"ttl": 3600,
"changetype": "REPLACE",
"records": [{"content": "3.218.167.115", "disabled": false}]
},
{
"name": "git.beyondtheuniverse.superviber.com.",
"type": "A",
"ttl": 300,
"changetype": "REPLACE",
"records": [{"content": "3.218.167.115", "disabled": false}]
}
]
}'
```
### Phase 4: Update GoDaddy Glue Records
At GoDaddy for superviber.com:
1. Update glue records to point ns1 and ns2 to 3.218.167.115
2. Set nameservers to ns1.superviber.com and ns2.superviber.com
### Phase 5: Verify
```bash
# Test directly against PowerDNS
dig @3.218.167.115 git.beyondtheuniverse.superviber.com A
# Test via public DNS (after propagation)
dig git.beyondtheuniverse.superviber.com A
```
---
## File Changes
| File | Change | Status |
|------|--------|--------|
| `hearth/terraform/minimal/main.tf` | Add DNS ports 53 UDP/TCP to security group | Done |
| `hearth/scripts/deploy-powerdns.sh` | Standalone PowerDNS deployment script | Done |
| `hearth/docs/rfcs/0003-powerdns-self-hosted.md` | This RFC | Done |
---
## Rollback Plan
If PowerDNS fails:
1. Update GoDaddy to use its default nameservers (restore from backup)
2. Or: Use Cloudflare (already partially configured)
The Elastic IP remains stable, so only NS records need updating.
---
## Test Plan
- [x] PowerDNS pod running in k3s
- [x] Port 53 UDP/TCP accessible from internet
- [x] `dig @3.218.167.115 superviber.com NS` returns ns1/ns2
- [x] `dig @3.218.167.115 git.beyondtheuniverse.superviber.com A` returns 3.218.167.115
- [x] GoDaddy glue records updated for all 5 domains (2026-01-24)
- [x] `dig @8.8.8.8 git.beyondtheuniverse.superviber.com A` resolves to 3.218.167.115
- [x] Forgejo accessible at https://git.beyondtheuniverse.superviber.com with valid TLS
---
## Future Work
1. **HA DNS** - Deploy on separate instance with its own EIP for redundancy
2. **DNSSEC** - Sign zones for security
3. **Backup zones** - Export zone files to S3 with daily backups
4. **DNS UI** - PowerDNS-Admin web interface for zone management
5. **Integration with Blue** - Manage DNS records as code via Blue
---
## References
- [PowerDNS Documentation](https://doc.powerdns.com/authoritative/)
- [coherence-mcp RFC 0046](../../coherence-mcp/docs/rfcs/0046-domain-email-migration.md)
- [coherence-mcp dns-zones-rfc0046.yaml](../../coherence-mcp/infra/kubernetes/powerdns/dns-zones-rfc0046.yaml)