- 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>
340 lines
9.7 KiB
Markdown
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)
|