feat(packaging): ODoH client Docker deploy
Single-container docker-compose recipe for running numa in ODoH client mode. Ships with a starter numa.toml pointing at odoh-relay.numa.rs paired with Cloudflare's ODoH target — two independent operators with distinct eTLD+1s, so the default passes numa's same-operator check. Exposes :53 UDP+TCP for LAN clients and :5380 for the dashboard + REST API. README covers prerequisites, deploy, verification, and the ODoH privacy boundary (relay sees IP, target sees query, neither sees both). Advertised alongside packaging/relay/ in the main README Docker section.
This commit is contained in:
@@ -125,6 +125,10 @@ docker run -d --name numa --network host \
|
|||||||
|
|
||||||
Multi-arch: `linux/amd64` and `linux/arm64`.
|
Multi-arch: `linux/amd64` and `linux/arm64`.
|
||||||
|
|
||||||
|
Turnkey compose recipes:
|
||||||
|
- [`packaging/client/`](packaging/client/) — ODoH client mode (anonymous DNS), Numa + starter `numa.toml`.
|
||||||
|
- [`packaging/relay/`](packaging/relay/) — public ODoH relay, Numa + Caddy + ACME.
|
||||||
|
|
||||||
## How It Compares
|
## How It Compares
|
||||||
|
|
||||||
| | Pi-hole | AdGuard Home | Unbound | Numa |
|
| | Pi-hole | AdGuard Home | Unbound | Numa |
|
||||||
|
|||||||
72
packaging/client/README.md
Normal file
72
packaging/client/README.md
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
# Numa ODoH Client — Docker deploy
|
||||||
|
|
||||||
|
Single-container deploy that runs Numa as an ODoH (RFC 9230) client: every
|
||||||
|
DNS query routes through an independent relay + target so neither operator
|
||||||
|
sees both your IP and your question. See the [ODoH integration doc][odoh]
|
||||||
|
for the full protocol and privacy trade-offs.
|
||||||
|
|
||||||
|
[odoh]: ../../docs/implementation/odoh-integration.md
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Docker + Docker Compose v2.
|
||||||
|
- Port 53 (UDP+TCP) free on the host — Numa listens there for DNS
|
||||||
|
clients on your LAN.
|
||||||
|
|
||||||
|
## Configure
|
||||||
|
|
||||||
|
The shipped `numa.toml` points at Numa's own public relay
|
||||||
|
(`odoh-relay.numa.rs`) paired with Cloudflare's ODoH target
|
||||||
|
(`odoh.cloudflare-dns.com`). That's two independent operators with
|
||||||
|
distinct eTLD+1s — the default configuration passes Numa's same-operator
|
||||||
|
check and works out of the box.
|
||||||
|
|
||||||
|
To use a different relay or target, edit `numa.toml` and adjust the URLs.
|
||||||
|
The `relay` and `target` must resolve to distinct operators or Numa
|
||||||
|
refuses to start.
|
||||||
|
|
||||||
|
## Deploy
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker compose up -d
|
||||||
|
docker compose logs -f numa # watch startup
|
||||||
|
```
|
||||||
|
|
||||||
|
The first query fires the bootstrap resolver + ODoH config fetch;
|
||||||
|
subsequent queries reuse the warm HTTP/2 connection.
|
||||||
|
|
||||||
|
## Point your devices at it
|
||||||
|
|
||||||
|
Set each device's DNS server to the IP of the Docker host. For a LAN-wide
|
||||||
|
rollout, set the DNS server in your router's DHCP config so every device
|
||||||
|
picks it up automatically.
|
||||||
|
|
||||||
|
Verify a query landed on the ODoH path:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
dig @<host-ip> example.com
|
||||||
|
curl http://<host-ip>:5380/stats | jq '.upstream_transport.odoh'
|
||||||
|
```
|
||||||
|
|
||||||
|
`upstream_transport.odoh` should increment on each query.
|
||||||
|
|
||||||
|
## What this does NOT buy you
|
||||||
|
|
||||||
|
ODoH protects the *path*, not the content:
|
||||||
|
|
||||||
|
- **The target (Cloudflare here) still sees the question.** It just
|
||||||
|
doesn't know it's you asking. If Cloudflare logs every ODoH query, the
|
||||||
|
query is still visible — it's simply unattributed.
|
||||||
|
- **The relay is a trusted party for availability.** A malicious relay
|
||||||
|
can drop or delay queries; it just can't read them.
|
||||||
|
- **Traffic analysis defeats small relays.** If you're the only client
|
||||||
|
talking to a relay, timing alone re-identifies you. Shared, busy relays
|
||||||
|
give better anonymity sets.
|
||||||
|
|
||||||
|
See the [ODoH integration doc][odoh] for more.
|
||||||
|
|
||||||
|
## Relay operator?
|
||||||
|
|
||||||
|
If you'd rather run your own relay (same binary, different mode), see
|
||||||
|
[`../relay/`](../relay/) — that package spins up a public-facing relay
|
||||||
|
with Caddy + ACME in front of it.
|
||||||
15
packaging/client/docker-compose.yml
Normal file
15
packaging/client/docker-compose.yml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
services:
|
||||||
|
numa:
|
||||||
|
image: ghcr.io/razvandimescu/numa:latest
|
||||||
|
command: ["/etc/numa/numa.toml"]
|
||||||
|
ports:
|
||||||
|
- "53:53/udp"
|
||||||
|
- "53:53/tcp"
|
||||||
|
- "5380:5380/tcp" # dashboard + REST API
|
||||||
|
volumes:
|
||||||
|
- ./numa.toml:/etc/numa/numa.toml:ro
|
||||||
|
- numa_data:/var/lib/numa
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
numa_data:
|
||||||
23
packaging/client/numa.toml
Normal file
23
packaging/client/numa.toml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# Numa — ODoH client mode (docker-compose starter).
|
||||||
|
# Sends every DNS query through an independent relay + target pair so
|
||||||
|
# neither operator sees both your IP and your question. See
|
||||||
|
# docs/implementation/odoh-integration.md for the protocol details and
|
||||||
|
# packaging/client/README.md for deploy notes.
|
||||||
|
|
||||||
|
[server]
|
||||||
|
bind_addr = "0.0.0.0:53"
|
||||||
|
api_bind_addr = "0.0.0.0"
|
||||||
|
data_dir = "/var/lib/numa"
|
||||||
|
|
||||||
|
[upstream]
|
||||||
|
mode = "odoh"
|
||||||
|
# Numa's own relay (Hetzner, systemd + Caddy). Swap to any other public
|
||||||
|
# ODoH relay if you'd rather not depend on a single operator; the protocol
|
||||||
|
# tolerates it, and Numa refuses same-operator relay+target by default.
|
||||||
|
relay = "https://odoh-relay.numa.rs/relay"
|
||||||
|
target = "https://odoh.cloudflare-dns.com/dns-query"
|
||||||
|
# strict = true (default). Relay failure → SERVFAIL, never silent downgrade.
|
||||||
|
|
||||||
|
[blocking]
|
||||||
|
enabled = true
|
||||||
|
# Default blocklist (Hagezi Pro). Edit the `lists` array to taste.
|
||||||
Reference in New Issue
Block a user