Files
Razvan Dimescu f7f35b3424 docs: lift user-facing guides to recipes/, drop dangling docs/ refs
docs/ is gitignored; references to docs/implementation/*.md from public
source, configs, and packaging were dead links outside the maintainer
machine. Adds four recipes (README, dnsdist-front, doh-on-lan,
odoh-upstream) under top-level recipes/ and repoints existing pointers.

- numa.toml, packaging/client/{README.md,numa.toml}: point to
  recipes/odoh-upstream.md.
- src/{bootstrap_resolver,forward,serve}.rs: reference issue #122
  directly (module scope is broader than the ODoH-specific recipe).
- src/health.rs: drop the §-ref; iOS HealthInfo remains named as the
  canonical consumer.
2026-04-24 15:09:16 +03:00
..

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 upstream recipe for the protocol details and the bootstrap-pinning trade-offs.

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

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:

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 for more.

Relay operator?

If you'd rather run your own relay (same binary, different mode), see ../relay/ — that package spins up a public-facing relay with Caddy + ACME in front of it.