feat(dns): add Technitium+Dockhand stack templates and end-to-end pfSense migration runbooks
- add docker-compose template for Technitium DNS and Dockhand - add required DNS/Dockhand env vars to .env.sample - add single-flow migration runbook (Pi Zero -> Pi 4 with same IP cutover) - add forced DNS and DoT/DoH hardening quick-entry guides - document wildcard DNS strategy and Technitium replacement mapping
This commit is contained in:
parent
84eaa82397
commit
6720cf8d62
10 changed files with 999 additions and 0 deletions
26
.env.sample
26
.env.sample
|
|
@ -66,3 +66,29 @@ EMAIL_PORT=
|
|||
EMAIL_HOST_USER=
|
||||
EMAIL_HOST_PASSWORD=
|
||||
DEFAULT_FROM_EMAIL=
|
||||
|
||||
# DNS stack (Technitium + Dockhand)
|
||||
# Bind IP for DNS service (use your DNS host IP, e.g. 192.168.60.5)
|
||||
DNS_BIND_IP=192.168.60.5
|
||||
TECHNITIUM_WEB_BIND_IP=0.0.0.0
|
||||
TECHNITIUM_WEB_PORT=5380
|
||||
TECHNITIUM_WEB_CONTAINER_PORT=5380
|
||||
|
||||
# Technitium
|
||||
TECHNITIUM_DNS_DOMAIN=home.arpa
|
||||
TECHNITIUM_ADMIN_PASSWORD=
|
||||
TECHNITIUM_RECURSION_MODE=UseSpecifiedNetworkACL
|
||||
TECHNITIUM_RECURSION_ACL=127.0.0.1/32,::1/128,192.168.0.0/16,10.0.0.0/8,172.16.0.0/12,fc00::/7
|
||||
TECHNITIUM_BLOCKING_TYPE=AnyAddress
|
||||
TECHNITIUM_BLOCKLIST_URLS=https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts
|
||||
TECHNITIUM_ALLOW_BLOCKING_REPORT=false
|
||||
TECHNITIUM_PREFER_IPV6=false
|
||||
|
||||
# Dockhand
|
||||
DOCKHAND_ENCRYPTION_KEY=
|
||||
DOCKHAND_PORT=3000
|
||||
DOCKHAND_BIND_IP=0.0.0.0
|
||||
# Host path containing compose stacks that Dockhand should manage
|
||||
DOCKHAND_STACKS_DIR=/opt/stacks
|
||||
# Find with: stat -c '%g' /var/run/docker.sock
|
||||
DOCKER_GID=999
|
||||
|
|
|
|||
|
|
@ -229,3 +229,7 @@ gramps.home.brianpooe.com {
|
|||
reverse_proxy 192.168.10.18:80
|
||||
}
|
||||
|
||||
seafile.home.brianpooe.com {
|
||||
import common
|
||||
reverse_proxy 192.168.10.17:8005
|
||||
}
|
||||
|
|
|
|||
119
dns/end-to-end-migration-runbook.md
Normal file
119
dns/end-to-end-migration-runbook.md
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
# End-to-End Migration Runbook (Pi-hole on Pi Zero 2 W -> Technitium + Dockhand on Pi 4)
|
||||
|
||||
Use this as the single flow document. Each step links to the deeper guide where needed.
|
||||
|
||||
## 1) Confirm baseline and keep rollback assets
|
||||
1. Keep your current backups safe:
|
||||
- pfSense backup XML
|
||||
- Pi-hole teleporter tarball
|
||||
2. Keep the old Pi Zero 2 W untouched until final cutover is stable.
|
||||
3. Review your analyzed current-state map:
|
||||
- [Current architecture analysis](/Users/luda/Documents/synology-docker-services/dns/pfsense-pihole-technitium-analysis.md)
|
||||
|
||||
## 2) Prepare the new Pi 4 with a temporary DNS IP
|
||||
1. Connect Pi 4 to the same Adblock VLAN (`192.168.60.0/24`).
|
||||
2. In pfSense, create a temporary DHCP static mapping for Pi 4, for example `192.168.60.6`.
|
||||
3. Confirm Pi 4 gets the temporary IP and has network reachability.
|
||||
|
||||
Why temporary IP first:
|
||||
- You can build and test everything without disturbing production DNS (`192.168.60.5`).
|
||||
|
||||
## 3) Configure compose environment and render stack
|
||||
1. Fill required values in `.env` (or environment file you use):
|
||||
- `DNS_BIND_IP=192.168.60.6` (temporary during staging)
|
||||
- `TECHNITIUM_ADMIN_PASSWORD=<strong-password>`
|
||||
- `DOCKHAND_ENCRYPTION_KEY=<long-random-string>`
|
||||
- `DOCKER_GID=<docker.sock group id>`
|
||||
- `DOCKHAND_STACKS_DIR=<path you want Dockhand to manage>`
|
||||
2. Render the compose from template:
|
||||
```bash
|
||||
./substitute_env.sh docker-compose-files/technitium-dockhand_template.yaml docker-compose-files/technitium-dockhand.yaml .env
|
||||
```
|
||||
3. Start the stack:
|
||||
```bash
|
||||
docker compose -f docker-compose-files/technitium-dockhand.yaml up -d
|
||||
```
|
||||
|
||||
Reference:
|
||||
- [Technitium + Dockhand deployment guide](/Users/luda/Documents/synology-docker-services/dns/technitium-dockhand-deployment.md)
|
||||
- [Compose template](/Users/luda/Documents/synology-docker-services/docker-compose-files/technitium-dockhand_template.yaml)
|
||||
|
||||
## 4) Configure Technitium to mirror current behavior
|
||||
1. Open Technitium UI on Pi 4 temporary IP:
|
||||
- `http://192.168.60.6:5380`
|
||||
2. Create primary zone `home.brianpooe.com`.
|
||||
3. Add:
|
||||
- `A` record: `caddy.home.brianpooe.com -> 192.168.10.5`
|
||||
- wildcard `CNAME`: `*.home.brianpooe.com -> caddy.home.brianpooe.com`
|
||||
4. Configure blocklist:
|
||||
- `https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts`
|
||||
5. Add allowlist entry:
|
||||
- `www.googleadservices.com`
|
||||
|
||||
Reference:
|
||||
- [Technitium cutover checklist](/Users/luda/Documents/synology-docker-services/dns/technitium-cutover-checklist.md)
|
||||
|
||||
## 5) Pre-cutover validation on temporary IP
|
||||
From clients (or directly from Pi 4), validate against `192.168.60.6`:
|
||||
```bash
|
||||
nslookup google.com 192.168.60.6
|
||||
nslookup proxmox.home.brianpooe.com 192.168.60.6
|
||||
nslookup switchlite8poe.home.brianpooe.com 192.168.60.6
|
||||
```
|
||||
|
||||
If these fail, do not proceed to IP swap.
|
||||
|
||||
## 6) Apply/confirm pfSense DNS enforcement policy
|
||||
If not already done, apply forced DNS interception for LAN + VLANs.
|
||||
|
||||
Reference (fast entry):
|
||||
- [Forced DNS quick entry](/Users/luda/Documents/synology-docker-services/dns/pfsense-forced-dns-quick-entry.md)
|
||||
|
||||
Reference (full explanation):
|
||||
- [Forced DNS all VLANs](/Users/luda/Documents/synology-docker-services/dns/pfsense-forced-dns-all-vlans.md)
|
||||
|
||||
Optional hardening for encrypted DNS bypass:
|
||||
- [DoT/DoH blocking quick entry](/Users/luda/Documents/synology-docker-services/dns/pfsense-dot-doh-blocking-quick-entry.md)
|
||||
|
||||
## 7) Safe same-IP handover (make Pi 4 become `192.168.60.5`)
|
||||
1. Schedule a short maintenance window.
|
||||
2. Stop/disconnect old Pi Zero 2 W first (avoid duplicate IP and ARP conflict).
|
||||
3. In pfSense DHCP static mappings (Adblock VLAN):
|
||||
- Move `192.168.60.5` reservation from old Pi MAC to new Pi 4 MAC.
|
||||
4. On Pi 4:
|
||||
- Renew DHCP lease or reboot networking/container host.
|
||||
5. Confirm Pi 4 now has `192.168.60.5`.
|
||||
6. If needed, clear stale ARP entry for `192.168.60.5` on pfSense.
|
||||
7. Update `.env`:
|
||||
- set `DNS_BIND_IP=192.168.60.5`
|
||||
8. Re-render and restart stack:
|
||||
```bash
|
||||
./substitute_env.sh docker-compose-files/technitium-dockhand_template.yaml docker-compose-files/technitium-dockhand.yaml .env
|
||||
docker compose -f docker-compose-files/technitium-dockhand.yaml up -d
|
||||
```
|
||||
|
||||
## 8) Post-cutover validation (production IP)
|
||||
Run tests from each VLAN segment:
|
||||
```bash
|
||||
nslookup google.com 192.168.60.5
|
||||
nslookup cloudflare.com 1.1.1.1
|
||||
nslookup proxmox.home.brianpooe.com 192.168.60.5
|
||||
nslookup switchlite8poe.home.brianpooe.com 192.168.60.5
|
||||
```
|
||||
|
||||
Expected:
|
||||
- DNS works from all VLANs.
|
||||
- Hardcoded public DNS on port 53 still resolves (redirected to your local DNS).
|
||||
- Local app hostnames resolve correctly.
|
||||
- Queries appear in Technitium logs.
|
||||
|
||||
## 9) Rollback (if needed)
|
||||
1. Stop Technitium stack on Pi 4.
|
||||
2. In pfSense, move `192.168.60.5` DHCP reservation back to old Pi Zero 2 W MAC.
|
||||
3. Start old Pi Zero 2 W.
|
||||
4. Validate DNS from clients.
|
||||
|
||||
## 10) After stabilization
|
||||
1. Update Caddy host references if you want `pihole.home...` renamed to `dns.home...`.
|
||||
2. Keep old Pi offline but available for a few days.
|
||||
3. Export Technitium backup after final state is verified.
|
||||
110
dns/pfsense-dot-doh-blocking-quick-entry.md
Normal file
110
dns/pfsense-dot-doh-blocking-quick-entry.md
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
# pfSense DoT/DoH Blocking Quick Entry (Copy-Style)
|
||||
|
||||
Use this after forced DNS redirect is in place.
|
||||
|
||||
Goal:
|
||||
- Block DNS-over-TLS (DoT) on port `853`.
|
||||
- Block known DNS-over-HTTPS (DoH) endpoints on port `443`.
|
||||
|
||||
## 0) Create aliases
|
||||
Path: `Firewall > Aliases`
|
||||
|
||||
Create network alias:
|
||||
- Name: `DNS_CLIENT_NETS`
|
||||
- Type: `Network(s)`
|
||||
- Values:
|
||||
- `LAN net`
|
||||
- `Office net` (OPT2)
|
||||
- `Family net` (OPT3)
|
||||
- `IoT net` (OPT4)
|
||||
- `Media net` (OPT5)
|
||||
- `Guest net` (OPT6)
|
||||
|
||||
Create host alias:
|
||||
- Name: `DOH_PROVIDERS`
|
||||
- Type: `Host(s)`
|
||||
- Values (FQDNs):
|
||||
- `dns.google`
|
||||
- `cloudflare-dns.com`
|
||||
- `one.one.one.one`
|
||||
- `security.cloudflare-dns.com`
|
||||
- `family.cloudflare-dns.com`
|
||||
- `dns.quad9.net`
|
||||
- `dns.nextdns.io`
|
||||
- `doh.opendns.com`
|
||||
- `doh.cleanbrowsing.org`
|
||||
- `dns.adguard-dns.com`
|
||||
- `unfiltered.adguard-dns.com`
|
||||
- `family.adguard-dns.com`
|
||||
- `dns.controld.com`
|
||||
- `doh.mullvad.net`
|
||||
|
||||
Notes:
|
||||
- This list is intentionally practical, not exhaustive.
|
||||
- You can add/remove providers later without editing firewall rules.
|
||||
|
||||
## 1) Floating rule: block DoT (853)
|
||||
Path: `Firewall > Rules > Floating` then `Add`
|
||||
|
||||
Set fields:
|
||||
- Action: `Block`
|
||||
- Quick: `checked`
|
||||
- Interface: `LAN, OPT2, OPT3, OPT4, OPT5, OPT6`
|
||||
- Direction: `out`
|
||||
- Address Family: `IPv4+IPv6`
|
||||
- Protocol: `TCP/UDP`
|
||||
- Source: `Single host or alias` = `DNS_CLIENT_NETS`
|
||||
- Source Port: `any`
|
||||
- Destination: `any`
|
||||
- Destination Port Range: `853 (DoT)`
|
||||
- Log: `checked` (recommended for tuning)
|
||||
- Description: `Block DoT 853 from client VLANs`
|
||||
|
||||
Save, but do not apply yet.
|
||||
|
||||
## 2) Floating rule: block known DoH (443)
|
||||
Path: `Firewall > Rules > Floating` then `Add`
|
||||
|
||||
Set fields:
|
||||
- Action: `Block`
|
||||
- Quick: `checked`
|
||||
- Interface: `LAN, OPT2, OPT3, OPT4, OPT5, OPT6`
|
||||
- Direction: `out`
|
||||
- Address Family: `IPv4+IPv6`
|
||||
- Protocol: `TCP`
|
||||
- Source: `Single host or alias` = `DNS_CLIENT_NETS`
|
||||
- Source Port: `any`
|
||||
- Destination: `Single host or alias` = `DOH_PROVIDERS`
|
||||
- Destination Port Range: `HTTPS (443)`
|
||||
- Log: `checked` (recommended for tuning)
|
||||
- Description: `Block known DoH 443 from client VLANs`
|
||||
|
||||
## 3) Rule order (critical)
|
||||
In `Firewall > Rules > Floating`:
|
||||
- Keep both block rules above broader floating pass rules.
|
||||
- Keep `Quick` enabled so they match immediately.
|
||||
|
||||
## 4) Apply and verify
|
||||
Apply:
|
||||
- `Save` + `Apply Changes`
|
||||
|
||||
Client-side checks (from LAN and one VLAN):
|
||||
```bash
|
||||
# DoT test (should fail)
|
||||
openssl s_client -connect 1.1.1.1:853 -brief
|
||||
|
||||
# DoH test (should fail or reset)
|
||||
curl -I https://dns.google/dns-query
|
||||
|
||||
# Normal DNS still works because of forced redirect
|
||||
nslookup google.com 8.8.8.8
|
||||
```
|
||||
|
||||
Expected:
|
||||
- DoT/DoH tests fail.
|
||||
- `nslookup ... 8.8.8.8` still resolves via your local DNS redirect path.
|
||||
|
||||
## 5) Important limitations
|
||||
- DoH blocking by known provider list is not complete; new/custom DoH endpoints can still bypass.
|
||||
- For strict control, use egress allow-listing and/or proxy-based controls.
|
||||
- Encrypted ClientHello (ECH) trends reduce hostname-based control over time.
|
||||
115
dns/pfsense-forced-dns-all-vlans.md
Normal file
115
dns/pfsense-forced-dns-all-vlans.md
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
# pfSense Rule Set: Force DNS on LAN + All VLANs
|
||||
|
||||
This runbook enforces DNS redirection to your local resolver (`192.168.60.5`) across:
|
||||
- LAN (`192.168.1.0/24`)
|
||||
- Office / OPT2 (`192.168.10.0/24`)
|
||||
- Family / OPT3 (`192.168.20.0/24`)
|
||||
- IoT / OPT4 (`192.168.30.0/24`)
|
||||
- Media / OPT5 (`192.168.40.0/24`)
|
||||
- Guest / OPT6 (`192.168.50.0/24`)
|
||||
|
||||
## Why this is needed
|
||||
Right now, redirect NAT is only on LAN. VLAN clients can still bypass your DNS filter by hardcoding public DNS on port 53.
|
||||
|
||||
## Prerequisites
|
||||
- DNS server IP remains `192.168.60.5` (Pi-hole now, Technitium later).
|
||||
- Existing inter-VLAN firewall policy still allows DNS to `192.168.60.5`.
|
||||
|
||||
## Step 1: Create pfSense aliases
|
||||
Path: `Firewall > Aliases`
|
||||
|
||||
Create host alias:
|
||||
- Name: `LOCAL_DNS`
|
||||
- Type: `Host(s)`
|
||||
- Value: `192.168.60.5`
|
||||
|
||||
Optional (cleaner descriptions):
|
||||
- Name: `DNS_CLIENT_INTERFACES`
|
||||
- Type: `Network(s)`
|
||||
- Values: `LAN net`, `Office net`, `Family net`, `IoT net`, `Media net`, `Guest net`
|
||||
|
||||
## Step 2: Create NAT Port Forward rules (one per interface)
|
||||
Path: `Firewall > NAT > Port Forward`
|
||||
|
||||
Create 6 rules, one each on: `LAN`, `OPT2`, `OPT3`, `OPT4`, `OPT5`, `OPT6`.
|
||||
|
||||
Use these exact fields for each rule:
|
||||
- Interface: `<that interface>`
|
||||
- Address Family: `IPv4`
|
||||
- Protocol: `TCP/UDP`
|
||||
- Source: `<that interface> net`
|
||||
- Source Port: `any`
|
||||
- Destination: `Single host or alias` = `LOCAL_DNS`
|
||||
- Invert Match (destination): `checked` (this means destination is NOT `192.168.60.5`)
|
||||
- Destination Port Range: `DNS (53)`
|
||||
- Redirect target IP: `LOCAL_DNS`
|
||||
- Redirect target port: `DNS (53)`
|
||||
- Description: `Force DNS to LOCAL_DNS (<interface>)`
|
||||
- Filter rule association: `Add associated filter rule`
|
||||
|
||||
Important:
|
||||
- Keep these 6 NAT rules near the top of the Port Forward list.
|
||||
- Do not apply this rule on `OPT7` (Adblock VLAN hosting the DNS server), to avoid self-redirection edge cases.
|
||||
|
||||
## Step 3: Clean up conflicting/obsolete firewall rules
|
||||
Path: `Firewall > Rules`
|
||||
|
||||
For LAN:
|
||||
- Disable or remove `Block all rogue DNS requests` (your current rule on LAN).
|
||||
|
||||
Reason:
|
||||
- With NAT redirection + associated pass rules, that block rule is redundant.
|
||||
- If moved above broad allows without careful matching, it can block legitimate redirected DNS.
|
||||
|
||||
For OPT3/OPT4/OPT5/OPT6:
|
||||
- Keep `Pi-hole` pass rules (or associated pass from NAT) above RFC1918 block rules.
|
||||
|
||||
## Step 4: Apply changes
|
||||
- Save and `Apply Changes` in NAT and Rules tabs.
|
||||
|
||||
## Step 5: Verify enforcement
|
||||
From one client in each segment (LAN + OPT2/3/4/5/6), run:
|
||||
|
||||
```bash
|
||||
nslookup google.com 8.8.8.8
|
||||
nslookup cloudflare.com 1.1.1.1
|
||||
```
|
||||
|
||||
Expected result:
|
||||
- Queries still succeed.
|
||||
- In your DNS server logs (Pi-hole/Technitium), source appears as the client, proving interception is active.
|
||||
|
||||
Also verify internal name resolution:
|
||||
|
||||
```bash
|
||||
nslookup proxmox.home.brianpooe.com
|
||||
nslookup switchlite8poe.home.brianpooe.com
|
||||
```
|
||||
|
||||
## Optional hardening (recommended)
|
||||
Port 53 forcing does not stop encrypted DNS bypass:
|
||||
- DoT uses TCP `853`
|
||||
- DoH uses HTTPS `443`
|
||||
|
||||
Minimum hardening:
|
||||
- Add block rules for outbound TCP/UDP `853` on client VLANs.
|
||||
|
||||
Advanced hardening:
|
||||
- Maintain alias lists of known DoH endpoints and block those on `443`.
|
||||
- Or enforce egress via proxy/policy controls if you need strict DNS governance.
|
||||
|
||||
## Rule logic diagram
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["Client on LAN/OPT2-OPT6"] --> B["Tries DNS to 8.8.8.8:53"]
|
||||
B --> C["pfSense NAT Port Forward\n(destination != 192.168.60.5)"]
|
||||
C --> D["Redirect to 192.168.60.5:53"]
|
||||
D --> E["Pi-hole or Technitium handles query"]
|
||||
```
|
||||
|
||||
## Fast entry version
|
||||
If you want one block per interface for direct UI entry, use:
|
||||
- `/Users/luda/Documents/synology-docker-services/dns/pfsense-forced-dns-quick-entry.md`
|
||||
|
||||
Related hardening quick-entry:
|
||||
- `/Users/luda/Documents/synology-docker-services/dns/pfsense-dot-doh-blocking-quick-entry.md`
|
||||
141
dns/pfsense-forced-dns-quick-entry.md
Normal file
141
dns/pfsense-forced-dns-quick-entry.md
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
# pfSense Forced DNS Quick Entry (Copy-Style)
|
||||
|
||||
Use this when entering rules manually in pfSense with minimal interpretation.
|
||||
|
||||
## 0) Create alias first
|
||||
Path: `Firewall > Aliases`
|
||||
|
||||
Create:
|
||||
- Name: `LOCAL_DNS`
|
||||
- Type: `Host(s)`
|
||||
- Value: `192.168.60.5`
|
||||
|
||||
## 1) NAT Port Forward rules (enter these 6)
|
||||
Path: `Firewall > NAT > Port Forward`
|
||||
|
||||
For each block below, click `Add` and use exactly these fields.
|
||||
|
||||
### Rule A: LAN
|
||||
- Interface: `LAN`
|
||||
- Address Family: `IPv4`
|
||||
- Protocol: `TCP/UDP`
|
||||
- Source: `LAN net`
|
||||
- Source Port: `any`
|
||||
- Destination: `Single host or alias` = `LOCAL_DNS`
|
||||
- Invert Match (destination): `checked`
|
||||
- Destination Port Range: `DNS (53)`
|
||||
- Redirect target IP: `LOCAL_DNS`
|
||||
- Redirect target port: `DNS (53)`
|
||||
- Description: `Force DNS to LOCAL_DNS (LAN)`
|
||||
- Filter rule association: `Add associated filter rule`
|
||||
|
||||
### Rule B: Office (OPT2)
|
||||
- Interface: `OPT2`
|
||||
- Address Family: `IPv4`
|
||||
- Protocol: `TCP/UDP`
|
||||
- Source: `OPT2 net`
|
||||
- Source Port: `any`
|
||||
- Destination: `Single host or alias` = `LOCAL_DNS`
|
||||
- Invert Match (destination): `checked`
|
||||
- Destination Port Range: `DNS (53)`
|
||||
- Redirect target IP: `LOCAL_DNS`
|
||||
- Redirect target port: `DNS (53)`
|
||||
- Description: `Force DNS to LOCAL_DNS (OPT2)`
|
||||
- Filter rule association: `Add associated filter rule`
|
||||
|
||||
### Rule C: Family (OPT3)
|
||||
- Interface: `OPT3`
|
||||
- Address Family: `IPv4`
|
||||
- Protocol: `TCP/UDP`
|
||||
- Source: `OPT3 net`
|
||||
- Source Port: `any`
|
||||
- Destination: `Single host or alias` = `LOCAL_DNS`
|
||||
- Invert Match (destination): `checked`
|
||||
- Destination Port Range: `DNS (53)`
|
||||
- Redirect target IP: `LOCAL_DNS`
|
||||
- Redirect target port: `DNS (53)`
|
||||
- Description: `Force DNS to LOCAL_DNS (OPT3)`
|
||||
- Filter rule association: `Add associated filter rule`
|
||||
|
||||
### Rule D: IoT (OPT4)
|
||||
- Interface: `OPT4`
|
||||
- Address Family: `IPv4`
|
||||
- Protocol: `TCP/UDP`
|
||||
- Source: `OPT4 net`
|
||||
- Source Port: `any`
|
||||
- Destination: `Single host or alias` = `LOCAL_DNS`
|
||||
- Invert Match (destination): `checked`
|
||||
- Destination Port Range: `DNS (53)`
|
||||
- Redirect target IP: `LOCAL_DNS`
|
||||
- Redirect target port: `DNS (53)`
|
||||
- Description: `Force DNS to LOCAL_DNS (OPT4)`
|
||||
- Filter rule association: `Add associated filter rule`
|
||||
|
||||
### Rule E: Media (OPT5)
|
||||
- Interface: `OPT5`
|
||||
- Address Family: `IPv4`
|
||||
- Protocol: `TCP/UDP`
|
||||
- Source: `OPT5 net`
|
||||
- Source Port: `any`
|
||||
- Destination: `Single host or alias` = `LOCAL_DNS`
|
||||
- Invert Match (destination): `checked`
|
||||
- Destination Port Range: `DNS (53)`
|
||||
- Redirect target IP: `LOCAL_DNS`
|
||||
- Redirect target port: `DNS (53)`
|
||||
- Description: `Force DNS to LOCAL_DNS (OPT5)`
|
||||
- Filter rule association: `Add associated filter rule`
|
||||
|
||||
### Rule F: Guest (OPT6)
|
||||
- Interface: `OPT6`
|
||||
- Address Family: `IPv4`
|
||||
- Protocol: `TCP/UDP`
|
||||
- Source: `OPT6 net`
|
||||
- Source Port: `any`
|
||||
- Destination: `Single host or alias` = `LOCAL_DNS`
|
||||
- Invert Match (destination): `checked`
|
||||
- Destination Port Range: `DNS (53)`
|
||||
- Redirect target IP: `LOCAL_DNS`
|
||||
- Redirect target port: `DNS (53)`
|
||||
- Description: `Force DNS to LOCAL_DNS (OPT6)`
|
||||
- Filter rule association: `Add associated filter rule`
|
||||
|
||||
Do not add this redirect on `OPT7` (DNS server VLAN).
|
||||
|
||||
## 2) Rule order checks (critical)
|
||||
|
||||
### NAT order
|
||||
In `Firewall > NAT > Port Forward`:
|
||||
- Keep all `Force DNS to LOCAL_DNS (...)` rules above other generic redirects.
|
||||
|
||||
### Firewall order
|
||||
In `Firewall > Rules` on each interface:
|
||||
- Keep associated DNS pass rule above any RFC1918 block rule.
|
||||
- On LAN, disable/remove your old `Block all rogue DNS requests` rule.
|
||||
|
||||
## 3) Apply and test
|
||||
|
||||
Apply:
|
||||
- `Save` + `Apply Changes` in NAT and Rules.
|
||||
|
||||
Test from one client per segment:
|
||||
```bash
|
||||
nslookup google.com 8.8.8.8
|
||||
nslookup cloudflare.com 1.1.1.1
|
||||
nslookup proxmox.home.brianpooe.com
|
||||
nslookup switchlite8poe.home.brianpooe.com
|
||||
```
|
||||
|
||||
Expected:
|
||||
- Queries succeed.
|
||||
- Requests appear in your DNS server logs (Pi-hole now / Technitium later).
|
||||
|
||||
## 4) Optional bypass hardening
|
||||
Port `53` interception does not stop encrypted DNS:
|
||||
- DoT: TCP `853`
|
||||
- DoH: HTTPS `443`
|
||||
|
||||
Minimum extra control:
|
||||
- Add block rules for outbound TCP/UDP `853` on LAN/OPT2/OPT3/OPT4/OPT5/OPT6.
|
||||
|
||||
Related hardening quick-entry:
|
||||
- `/Users/luda/Documents/synology-docker-services/dns/pfsense-dot-doh-blocking-quick-entry.md`
|
||||
183
dns/pfsense-pihole-technitium-analysis.md
Normal file
183
dns/pfsense-pihole-technitium-analysis.md
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
# pfSense + Pi-hole DNS Architecture Analysis and Technitium Mapping
|
||||
|
||||
## Scope and source files
|
||||
- pfSense backup: `/Users/luda/Downloads/config-pfSense.home.arpa-20260301104624.xml`
|
||||
- Pi-hole teleporter: `/Users/luda/Downloads/pi-hole-raspberrypihole-teleporter_2026-03-01_08-38-24.tar.gz`
|
||||
|
||||
## Executive summary
|
||||
Your current design is solid and intentional:
|
||||
- pfSense DHCP points clients to Pi-hole (`192.168.60.5`) on all major VLANs.
|
||||
- Pi-hole serves local split-DNS for reverse-proxied apps via `caddy.home.brianpooe.com`.
|
||||
- Pi-hole forwards upstream DNS to pfSense (`192.168.1.1`), then pfSense uses its configured upstream resolvers.
|
||||
- Cross-VLAN isolation is enforced, with explicit exceptions for DNS to Pi-hole and selective access to Caddy.
|
||||
|
||||
Technitium can replace Pi-hole cleanly. The biggest migration requirement is preserving the same service IP (`192.168.60.5`) or updating every dependent DHCP/firewall/NAT reference.
|
||||
|
||||
## Current network and DNS components
|
||||
- VLAN interfaces on pfSense:
|
||||
- LAN: `192.168.1.0/24`
|
||||
- Office (OPT2): `192.168.10.0/24`
|
||||
- Family (OPT3): `192.168.20.0/24`
|
||||
- IoT (OPT4): `192.168.30.0/24`
|
||||
- Media (OPT5): `192.168.40.0/24`
|
||||
- Guest (OPT6): `192.168.50.0/24`
|
||||
- Adblock (OPT7): `192.168.60.0/24`
|
||||
- Pi-hole static DHCP mapping on Adblock VLAN:
|
||||
- `raspberrypihole` -> `192.168.60.5`
|
||||
- Caddy static DHCP mapping on Office VLAN:
|
||||
- `raspberrypiproxy` -> `192.168.10.5`
|
||||
|
||||
## How pfSense and Pi-hole are connected
|
||||
|
||||
### 1. DHCP dependency (clients -> Pi-hole)
|
||||
pfSense DHCP advertises `192.168.60.5` as DNS server for:
|
||||
- LAN
|
||||
- Office
|
||||
- Family
|
||||
- IoT
|
||||
- Media
|
||||
- Guest
|
||||
|
||||
This is the primary coupling between pfSense and Pi-hole.
|
||||
|
||||
### 2. DNS forwarding dependency (Pi-hole -> pfSense)
|
||||
From Pi-hole backup:
|
||||
- `PIHOLE_DNS_1=192.168.1.1`
|
||||
- `PIHOLE_DNS_2=192.168.1.1`
|
||||
- `server=192.168.1.1`
|
||||
|
||||
So every query path is:
|
||||
1. Client -> Pi-hole (`192.168.60.5`)
|
||||
2. Pi-hole -> pfSense (`192.168.1.1`)
|
||||
3. pfSense DNS path -> upstream resolvers (for example `1.1.1.1` / `8.8.8.8` as configured in this backup)
|
||||
|
||||
Note:
|
||||
- You confirmed your active setup is not using Unbound as part of the current design.
|
||||
|
||||
### 3. NAT DNS interception on pfSense
|
||||
There is one DNS redirect NAT rule:
|
||||
- Interface: `LAN` only
|
||||
- Match: any source, destination `NOT 192.168.60.5`, port `53` TCP/UDP
|
||||
- Redirect target: `192.168.60.5:53`
|
||||
|
||||
Important implication: forced redirect is currently configured only for LAN traffic, not all VLANs.
|
||||
|
||||
### 4. Firewall policy that enables segmented DNS
|
||||
For Family/IoT/Media/Guest VLANs, you have explicit pass rules allowing DNS to `192.168.60.5:53` plus RFC1918 block rules. This is why segmented VLANs can still use centralized DNS.
|
||||
|
||||
### 4a. Per-segment behavior (what is effectively enforced)
|
||||
| Segment | DHCP DNS handed out | Explicit allow to Pi-hole `192.168.60.5:53` | RFC1918 block rule | Caddy cross-VLAN allow (`192.168.10.5`) | DNS force-redirect NAT |
|
||||
|---|---|---|---|---|---|
|
||||
| LAN | Yes | Associated NAT pass rule exists | Yes (`Block all rogue DNS requests`) | N/A (LAN allow-any) | Yes (LAN only) |
|
||||
| Office (OPT2) | Yes | No dedicated rule (allow-any policy) | No | Implicit via allow-any | No |
|
||||
| Family (OPT3) | Yes | Yes | Yes | Yes | No |
|
||||
| IoT (OPT4) | Yes | Yes | Yes | No | No |
|
||||
| Media (OPT5) | Yes | Yes | Yes | Yes | No |
|
||||
| Guest (OPT6) | Yes | Yes | Yes | No | No |
|
||||
|
||||
Interpretation:
|
||||
- DNS centralization is primarily driven by DHCP, not global NAT interception.
|
||||
- Isolation model is strong on OPT3/4/5/6 due to RFC1918 blocking plus explicit exceptions.
|
||||
- Office VLAN is the least restricted of the VLANs shown.
|
||||
|
||||
### 5. Local service naming through Pi-hole
|
||||
Pi-hole local DNS is doing two jobs:
|
||||
- `custom.list` has:
|
||||
- `caddy.home.brianpooe.com -> 192.168.10.5`
|
||||
- `05-pihole-custom-cname.conf` has 26 CNAME entries, e.g.:
|
||||
- `proxmox.home.brianpooe.com -> caddy.home.brianpooe.com`
|
||||
- `radarr.home.brianpooe.com -> caddy.home.brianpooe.com`
|
||||
- `paperless-ngx.home.brianpooe.com -> caddy.home.brianpooe.com`
|
||||
|
||||
### 6. Ad-blocking state
|
||||
From Pi-hole teleporter:
|
||||
- Adlists: 1 (StevenBlack hosts)
|
||||
- Exact allowlist: 1 (`www.googleadservices.com`)
|
||||
- Exact/regex deny lists: none
|
||||
- Client groups: default group only (no per-client policy split)
|
||||
|
||||
## Diagram: current flow
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["Clients on VLANs\n192.168.1/10/20/30/40/50"] --> B["pfSense DHCP\nDNS server handed out: 192.168.60.5"]
|
||||
A --> C["Pi-hole\n192.168.60.5 (Adblock VLAN)"]
|
||||
C --> D["pfSense DNS service\n192.168.1.1"]
|
||||
D --> E["Upstream resolvers\n(e.g. 1.1.1.1 / 8.8.8.8)"]
|
||||
|
||||
A --> F["DNS to external resolvers:53"]
|
||||
F -. "LAN only NAT redirect" .-> C
|
||||
|
||||
C --> G["A record\ncaddy.home.brianpooe.com -> 192.168.10.5"]
|
||||
C --> H["26 CNAMEs\n*.home.brianpooe.com -> caddy.home.brianpooe.com"]
|
||||
A --> I["Caddy reverse proxy\n192.168.10.5"]
|
||||
```
|
||||
|
||||
## Where Gemini was correct vs needs correction
|
||||
|
||||
### Correct
|
||||
- Technitium can replace Pi-hole and keep most pfSense-side design.
|
||||
- Your CNAME-based app routing can be moved cleanly to Technitium zones.
|
||||
- VLAN segmentation + explicit DNS access pattern is present.
|
||||
- Pi-hole upstream currently points at pfSense (`192.168.1.1`).
|
||||
|
||||
### Needs correction
|
||||
- "Forced DNS intercept" is not global right now:
|
||||
- NAT redirect rule is only on `LAN` interface.
|
||||
- "Block rogue DNS" on LAN exists, but appears below broad LAN allow rules:
|
||||
- With default allow-first ordering, this specific block may not be doing what its label suggests.
|
||||
- VLANs are not force-redirecting DNS to Pi-hole by NAT:
|
||||
- They rely on DHCP DNS assignment and explicit allow to Pi-hole.
|
||||
- Direct DNS to public resolvers on port 53 may still be possible from some VLANs unless separately blocked/redirected.
|
||||
|
||||
## Technitium mapping (feature-by-feature)
|
||||
|
||||
| Current function | Current implementation | Technitium equivalent | Migration action |
|
||||
|---|---|---|---|
|
||||
| Central DNS IP | Pi-hole at `192.168.60.5` | Technitium at `192.168.60.5` | Keep same IP to avoid touching multiple pfSense dependencies |
|
||||
| DHCP DNS handout | pfSense DHCP -> `192.168.60.5` on VLANs | Same | No change needed if IP unchanged |
|
||||
| Cross-VLAN DNS permit | Firewall pass rules to `192.168.60.5:53` | Same | No change needed |
|
||||
| Forced DNS on LAN | NAT redirect to `192.168.60.5:53` | Same | No change needed |
|
||||
| Local app DNS | `custom.list` + CNAME file | Authoritative zone in Technitium | Create `home.brianpooe.com` zone, add A for `caddy`, add CNAME records |
|
||||
| Ad block list | StevenBlack in Pi-hole | Block List in Technitium | Add same list URL |
|
||||
| Allowlist | `www.googleadservices.com` | Allow Zone / Whitelist | Add same domain |
|
||||
| Upstream resolution | Pi-hole -> pfSense (`192.168.1.1`) -> upstream resolvers | Technitium recursive resolver or forwarders | Choose direct recursion or keep pfSense as upstream intentionally |
|
||||
|
||||
## Diagram: proposed with Technitium
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["Clients on VLANs"] --> B["pfSense DHCP\nDNS server: 192.168.60.5"]
|
||||
A --> C["Technitium DNS\n192.168.60.5"]
|
||||
C --> D["Technitium recursion\n(root/TLD authoritative path)"]
|
||||
D --> E["Internet DNS hierarchy"]
|
||||
|
||||
C --> F["Zone: home.brianpooe.com"]
|
||||
F --> G["A: caddy -> 192.168.10.5"]
|
||||
F --> H["CNAME: app hosts -> caddy.home.brianpooe.com"]
|
||||
|
||||
A --> I["Caddy reverse proxy\n192.168.10.5"]
|
||||
```
|
||||
|
||||
## Practical migration checklist
|
||||
1. Deploy Technitium on Raspberry Pi with IP `192.168.60.5` (or cutover with brief IP swap).
|
||||
2. Recreate `home.brianpooe.com` zone:
|
||||
- Add `A` record `caddy.home.brianpooe.com -> 192.168.10.5`
|
||||
- Add all 26 CNAME records from current Pi-hole file.
|
||||
3. Import StevenBlack block list and add allow entry for `www.googleadservices.com`.
|
||||
4. Validate DNS from each VLAN (`dig/nslookup`) for:
|
||||
- public domain resolution
|
||||
- blocked ad domain
|
||||
- `proxmox.home.brianpooe.com` and other internal names
|
||||
5. Cut traffic to Technitium (same IP preferred).
|
||||
6. Optional hardening after cutover:
|
||||
- add DNS redirect/block on VLANs beyond LAN if you want true forced DNS everywhere
|
||||
- review LAN DNS block rule order
|
||||
- decide whether to allow/deny direct DNS-over-TLS and DNS-over-HTTPS egress
|
||||
|
||||
## Key risks to keep in mind
|
||||
- If Technitium is not on `192.168.60.5`, you must update DHCP options, firewall rules, and NAT target references.
|
||||
- DNS interception in your current config is partial (LAN-focused), not universal across all VLANs.
|
||||
- IPv6 DNS policy is separate; current forced DNS logic is IPv4 (`inet`) only.
|
||||
|
||||
## Companion runbook
|
||||
For step-by-step cutover execution and wildcard-vs-explicit record strategy, see:
|
||||
- `/Users/luda/Documents/synology-docker-services/dns/technitium-cutover-checklist.md`
|
||||
156
dns/technitium-cutover-checklist.md
Normal file
156
dns/technitium-cutover-checklist.md
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
# Technitium Cutover Checklist (From Current pfSense + Pi-hole)
|
||||
|
||||
Start-here master flow:
|
||||
- `/Users/luda/Documents/synology-docker-services/dns/end-to-end-migration-runbook.md`
|
||||
|
||||
## Short answer: do you need 26 CNAME records?
|
||||
No, not if all your app hostnames should resolve to Caddy.
|
||||
|
||||
You can replace most manual CNAMEs with:
|
||||
- `A` record: `caddy.home.brianpooe.com -> 192.168.10.5`
|
||||
- wildcard `CNAME`: `*.home.brianpooe.com -> caddy.home.brianpooe.com`
|
||||
|
||||
This removes the need to add a new DNS record every time you add a new proxied service.
|
||||
|
||||
## Wildcard caveats (important)
|
||||
- Wildcard does not cover the zone apex itself (`home.brianpooe.com`).
|
||||
- Explicit records still override wildcard (this is good; you can keep special cases).
|
||||
- A typo hostname will still resolve to Caddy; Caddy may return 404/cert mismatch until that host is configured there.
|
||||
- Keep your reverse-proxy security policy in Caddy (host matching/auth), because DNS wildcard is broad by design.
|
||||
|
||||
## What your current data says
|
||||
From Pi-hole backup:
|
||||
- `caddy.home.brianpooe.com -> 192.168.10.5`
|
||||
- 26 CNAMEs currently pointing to `caddy.home.brianpooe.com`
|
||||
|
||||
From your Caddyfile host blocks:
|
||||
- 28 hostnames are defined in file.
|
||||
- You marked these as no longer in use (excluded from active migration scope):
|
||||
- `jellyseerr.home.brianpooe.com`
|
||||
- `ollama.home.brianpooe.com`
|
||||
- `paperless-ai.home.brianpooe.com`
|
||||
- Active hostnames in scope: 25.
|
||||
- 1 active hostname exists in Caddy but is missing from Pi-hole CNAME list:
|
||||
- `switchlite8poe.home.brianpooe.com`
|
||||
- 2 hostnames are in Pi-hole CNAME list but not currently in Caddy:
|
||||
- `seafile.home.brianpooe.com`
|
||||
- `tplink8pe.home.brianpooe.com`
|
||||
|
||||
A wildcard record closes this drift gap permanently.
|
||||
|
||||
## Cutover plan
|
||||
|
||||
### Docker stack files in this repo
|
||||
- Compose template: `/Users/luda/Documents/synology-docker-services/docker-compose-files/technitium-dockhand_template.yaml`
|
||||
- Deployment guide: `/Users/luda/Documents/synology-docker-services/dns/technitium-dockhand-deployment.md`
|
||||
|
||||
### 1. Keep network references stable
|
||||
- Keep Technitium service IP as `192.168.60.5`.
|
||||
- This avoids changing pfSense DHCP DNS settings, firewall allow rules, and LAN DNS redirect NAT target.
|
||||
|
||||
### 2. Deploy Technitium on Raspberry Pi
|
||||
- Run Technitium container on the Adblock VLAN network reachable as `192.168.60.5`.
|
||||
- Ensure TCP/UDP `53` reaches this container from all intended VLANs.
|
||||
|
||||
### 3. Create primary zone
|
||||
- Zone name: `home.brianpooe.com`
|
||||
- Zone type: Primary
|
||||
|
||||
### 4. Add DNS records (choose one model)
|
||||
|
||||
#### Model A: wildcard-first (recommended)
|
||||
Create only these core records first:
|
||||
- `A` `caddy.home.brianpooe.com` -> `192.168.10.5`
|
||||
- `CNAME` `*.home.brianpooe.com` -> `caddy.home.brianpooe.com`
|
||||
|
||||
Optional explicit records for readability (not required if wildcard exists):
|
||||
- `CNAME` `pihole.home.brianpooe.com` -> `caddy.home.brianpooe.com`
|
||||
- `CNAME` `proxmox.home.brianpooe.com` -> `caddy.home.brianpooe.com`
|
||||
- etc.
|
||||
|
||||
#### Model B: explicit-only (current behavior equivalent)
|
||||
If you prefer explicit control, create:
|
||||
- `A` `caddy.home.brianpooe.com` -> `192.168.10.5`
|
||||
- CNAME records for each service:
|
||||
- `pihole.home.brianpooe.com`
|
||||
- `proxmox.home.brianpooe.com`
|
||||
- `nas.home.brianpooe.com`
|
||||
- `bazarr.home.brianpooe.com`
|
||||
- `emby.home.brianpooe.com`
|
||||
- `flaresolverr.home.brianpooe.com`
|
||||
- `gluetun.home.brianpooe.com`
|
||||
- `prowlarr.home.brianpooe.com`
|
||||
- `qbittorrent.home.brianpooe.com`
|
||||
- `radarr.home.brianpooe.com`
|
||||
- `sabnzbd.home.brianpooe.com`
|
||||
- `sonarr.home.brianpooe.com`
|
||||
- `tplink8pe.home.brianpooe.com`
|
||||
- `tplink16de.home.brianpooe.com`
|
||||
- `pbs.home.brianpooe.com`
|
||||
- `paperless-ngx.home.brianpooe.com`
|
||||
- `it-tools.home.brianpooe.com`
|
||||
- `bento-pdf.home.brianpooe.com`
|
||||
- `beszel.home.brianpooe.com`
|
||||
- `immich.home.brianpooe.com`
|
||||
- `speedtest.home.brianpooe.com`
|
||||
- `seafile.home.brianpooe.com`
|
||||
- `gramps.home.brianpooe.com`
|
||||
- `ytd.home.brianpooe.com`
|
||||
- `dav.home.brianpooe.com`
|
||||
- `seerr.home.brianpooe.com`
|
||||
|
||||
If using explicit-only, also add active Caddy hosts that currently have no Pi-hole entry:
|
||||
- `switchlite8poe.home.brianpooe.com`
|
||||
|
||||
### 5. Recreate filtering behavior
|
||||
- Add blocklist URL:
|
||||
- `https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts`
|
||||
- Add allowlist domain:
|
||||
- `www.googleadservices.com`
|
||||
|
||||
### 6. Choose upstream strategy in Technitium
|
||||
Pick one:
|
||||
- Least-change: forward to pfSense (`192.168.1.1`) first.
|
||||
- Cleaner path: enable direct recursive resolution in Technitium.
|
||||
|
||||
### 7. Validate before full cutover
|
||||
From a client in each VLAN, test:
|
||||
- `nslookup proxmox.home.brianpooe.com 192.168.60.5`
|
||||
- `nslookup switchlite8poe.home.brianpooe.com 192.168.60.5`
|
||||
- `nslookup google.com 192.168.60.5`
|
||||
- `nslookup www.googleadservices.com 192.168.60.5` (should be allowed)
|
||||
|
||||
Also test one blocked ad/tracker domain from query logs.
|
||||
|
||||
### 8. Cutover
|
||||
- Stop Pi-hole service on `192.168.60.5`.
|
||||
- Start Technitium on `192.168.60.5`.
|
||||
- Confirm DNS responses and Caddy app access from each VLAN.
|
||||
|
||||
### 9. Rollback plan
|
||||
If anything fails:
|
||||
- Stop Technitium.
|
||||
- Start Pi-hole back on `192.168.60.5`.
|
||||
- Existing pfSense integration should recover immediately since IP dependencies are unchanged.
|
||||
|
||||
## Decision recommendation for your setup
|
||||
Use Model A (wildcard-first).
|
||||
|
||||
Reason:
|
||||
- Your Caddy host inventory changes over time and is already ahead of Pi-hole CNAME entries.
|
||||
- Wildcard removes manual DNS drift while preserving the same user-facing URLs.
|
||||
|
||||
## DNS + proxy interaction (with wildcard)
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["Client queries any host\n*.home.brianpooe.com"] --> B["Technitium wildcard CNAME"]
|
||||
B --> C["caddy.home.brianpooe.com"]
|
||||
C --> D["A record 192.168.10.5"]
|
||||
D --> E["Caddy routes by Host header\nto matching app"]
|
||||
```
|
||||
|
||||
## Related pfSense enforcement runbook
|
||||
For exact NAT/firewall settings to force DNS across LAN + VLANs, see:
|
||||
- `/Users/luda/Documents/synology-docker-services/dns/pfsense-forced-dns-all-vlans.md`
|
||||
- `/Users/luda/Documents/synology-docker-services/dns/pfsense-forced-dns-quick-entry.md`
|
||||
- `/Users/luda/Documents/synology-docker-services/dns/pfsense-dot-doh-blocking-quick-entry.md`
|
||||
61
dns/technitium-dockhand-deployment.md
Normal file
61
dns/technitium-dockhand-deployment.md
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
# Technitium + Dockhand Docker Deployment Guide
|
||||
|
||||
This guide deploys Technitium DNS and Dockhand using:
|
||||
- `/Users/luda/Documents/synology-docker-services/docker-compose-files/technitium-dockhand_template.yaml`
|
||||
|
||||
## 1) Prepare env values
|
||||
Update your `.env` with these required values:
|
||||
- `DNS_BIND_IP=192.168.60.5`
|
||||
- `TECHNITIUM_ADMIN_PASSWORD=<strong-password>`
|
||||
- `DOCKHAND_ENCRYPTION_KEY=<long-random-string>`
|
||||
- `DOCKHAND_STACKS_DIR=<host path containing compose files>`
|
||||
- `DOCKER_GID=<docker.sock group id>`
|
||||
|
||||
Tip:
|
||||
- `DOCKER_GID` command: `stat -c '%g' /var/run/docker.sock`
|
||||
|
||||
## 2) Render compose from template
|
||||
Run:
|
||||
```bash
|
||||
./substitute_env.sh docker-compose-files/technitium-dockhand_template.yaml docker-compose-files/technitium-dockhand.yaml .env
|
||||
```
|
||||
|
||||
## 3) Start stack
|
||||
Run:
|
||||
```bash
|
||||
docker compose -f docker-compose-files/technitium-dockhand.yaml up -d
|
||||
```
|
||||
|
||||
## 4) First-time Technitium setup (required)
|
||||
Open:
|
||||
- `http://<DNS_BIND_IP>:5380`
|
||||
|
||||
Then configure:
|
||||
1. Create primary zone `home.brianpooe.com`.
|
||||
2. Add `A` record: `caddy.home.brianpooe.com -> 192.168.10.5`.
|
||||
3. Add wildcard `CNAME`: `*.home.brianpooe.com -> caddy.home.brianpooe.com`.
|
||||
4. Add allowlist equivalent for `www.googleadservices.com`.
|
||||
|
||||
Reference details:
|
||||
- `/Users/luda/Documents/synology-docker-services/dns/technitium-cutover-checklist.md`
|
||||
|
||||
## 5) pfSense integration
|
||||
Keep DNS server IP as `192.168.60.5` to avoid changing existing DHCP and firewall dependencies.
|
||||
|
||||
For forced DNS across all VLANs:
|
||||
- `/Users/luda/Documents/synology-docker-services/dns/pfsense-forced-dns-quick-entry.md`
|
||||
|
||||
For DoT/DoH hardening:
|
||||
- `/Users/luda/Documents/synology-docker-services/dns/pfsense-dot-doh-blocking-quick-entry.md`
|
||||
|
||||
## 6) Validation
|
||||
Run from clients on each VLAN:
|
||||
```bash
|
||||
nslookup google.com 192.168.60.5
|
||||
nslookup proxmox.home.brianpooe.com 192.168.60.5
|
||||
nslookup switchlite8poe.home.brianpooe.com 192.168.60.5
|
||||
```
|
||||
|
||||
## 7) Common gotchas
|
||||
- If Technitium fails to bind port 53, check for other DNS services on the host (for example `systemd-resolved`, `dnsmasq`, old Pi-hole container).
|
||||
- If Dockhand cannot manage containers, verify `DOCKER_GID` and docker socket mount.
|
||||
84
docker-compose-files/technitium-dockhand_template.yaml
Normal file
84
docker-compose-files/technitium-dockhand_template.yaml
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
version: '3.8'
|
||||
|
||||
# Technitium DNS + Dockhand stack
|
||||
# - Technitium replaces Pi-hole + Unbound behavior (blocking + recursion)
|
||||
# - Dockhand manages container stacks from a web UI
|
||||
|
||||
services:
|
||||
technitium-dns:
|
||||
image: technitium/dns-server:latest
|
||||
container_name: technitium-dns
|
||||
restart: unless-stopped
|
||||
labels:
|
||||
- "com.synology.stack=network"
|
||||
- "com.synology.service=technitium-dns"
|
||||
environment:
|
||||
TZ: "{{TZ}}"
|
||||
# Domain shown in Technitium UI defaults
|
||||
DNS_SERVER_DOMAIN: "{{TECHNITIUM_DNS_DOMAIN:-home.arpa}}"
|
||||
|
||||
# Required: set a strong admin password in .env
|
||||
DNS_SERVER_ADMIN_PASSWORD: "{{TECHNITIUM_ADMIN_PASSWORD}}"
|
||||
|
||||
# Recursive resolver mode (Technitium does root recursion)
|
||||
DNS_SERVER_RECURSION: "{{TECHNITIUM_RECURSION_MODE:-UseSpecifiedNetworkACL}}"
|
||||
DNS_SERVER_RECURSION_NETWORK_ACL: "{{TECHNITIUM_RECURSION_ACL:-127.0.0.1/32,::1/128,192.168.0.0/16,10.0.0.0/8,172.16.0.0/12,fc00::/7}}"
|
||||
|
||||
# Pi-hole-like ad blocking behavior
|
||||
DNS_SERVER_BLOCKING_TYPE: "{{TECHNITIUM_BLOCKING_TYPE:-AnyAddress}}"
|
||||
DNS_SERVER_BLOCK_LIST_URLS: "{{TECHNITIUM_BLOCKLIST_URLS:-https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts}}"
|
||||
DNS_SERVER_ALLOW_TXT_BLOCKING_REPORT: "{{TECHNITIUM_ALLOW_BLOCKING_REPORT:-false}}"
|
||||
|
||||
# Operational defaults
|
||||
DNS_SERVER_PREFER_IPV6: "{{TECHNITIUM_PREFER_IPV6:-false}}"
|
||||
DNS_SERVER_WEB_SERVICE_HTTP_PORT: "{{TECHNITIUM_WEB_CONTAINER_PORT:-5380}}"
|
||||
volumes:
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
- {{DOCKERCONFDIR}}/technitium:/etc/dns
|
||||
ports:
|
||||
# Core DNS service
|
||||
- "{{DNS_BIND_IP:-0.0.0.0}}:53:53/udp"
|
||||
- "{{DNS_BIND_IP:-0.0.0.0}}:53:53/tcp"
|
||||
# Technitium web admin
|
||||
- "{{TECHNITIUM_WEB_BIND_IP:-0.0.0.0}}:{{TECHNITIUM_WEB_PORT:-5380}}:{{TECHNITIUM_WEB_CONTAINER_PORT:-5380}}/tcp"
|
||||
# Optional encrypted DNS listeners (enable only if you intend to use them)
|
||||
# - "{{DNS_BIND_IP:-0.0.0.0}}:853:853/tcp" # DoT
|
||||
# - "{{DNS_BIND_IP:-0.0.0.0}}:443:443/tcp" # DoH
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-file: "{{DOCKERLOGGING_MAXFILE}}"
|
||||
max-size: "{{DOCKERLOGGING_MAXSIZE}}"
|
||||
|
||||
dockhand:
|
||||
image: ghcr.io/dockhand-oss/dockhand:latest
|
||||
container_name: dockhand
|
||||
restart: unless-stopped
|
||||
labels:
|
||||
- "com.synology.stack=management"
|
||||
- "com.synology.service=dockhand"
|
||||
environment:
|
||||
TZ: "{{TZ}}"
|
||||
# Required: set a strong random value in .env
|
||||
ENCRYPTION_KEY: "{{DOCKHAND_ENCRYPTION_KEY}}"
|
||||
PORT: "3000"
|
||||
DATA_DIR: "/opt/dockhand"
|
||||
PUID: "{{PUID}}"
|
||||
PGID: "{{PGID}}"
|
||||
group_add:
|
||||
- "{{DOCKER_GID:-999}}"
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- {{DOCKERCONFDIR}}/dockhand:/opt/dockhand
|
||||
- {{DOCKHAND_STACKS_DIR:-/opt/stacks}}:/opt/stacks
|
||||
ports:
|
||||
- "{{DOCKHAND_BIND_IP:-0.0.0.0}}:{{DOCKHAND_PORT:-3000}}:3000/tcp"
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-file: "{{DOCKERLOGGING_MAXFILE}}"
|
||||
max-size: "{{DOCKERLOGGING_MAXSIZE}}"
|
||||
Loading…
Add table
Reference in a new issue