From be60f6ccbc33189d34bd5e02d894aec69ba6fe8c Mon Sep 17 00:00:00 2001 From: Razvan Dimescu Date: Mon, 20 Apr 2026 15:44:29 +0300 Subject: [PATCH] chore(packaging): docker-compose + Caddyfile for ODoH relay deploy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two-container deploy: Caddy terminates TLS (auto-provisions Let's Encrypt via ACME) and reverse-proxies to a Numa relay on an internal Docker network. The relay never reads sealed payloads; Caddy's access log is discarded so per-request observability doesn't defeat the oblivious property. Validated against Hetzner CX22 + DNS at odoh-relay.numa.rs: - TLS-ALPN-01 challenge succeeded on first attempt - /health returned the relay's counter block - End-to-end ODoH client → relay → Cloudflare works Operators only need to: set a DNS A record, edit Caddyfile's hostname, docker compose up -d. README walks through the steps and the DNSCrypt v3/odoh-relays.md submission to claim a public listing. --- packaging/relay/Caddyfile | 15 ++++++++++ packaging/relay/README.md | 48 ++++++++++++++++++++++++++++++ packaging/relay/docker-compose.yml | 26 ++++++++++++++++ 3 files changed, 89 insertions(+) create mode 100644 packaging/relay/Caddyfile create mode 100644 packaging/relay/README.md create mode 100644 packaging/relay/docker-compose.yml diff --git a/packaging/relay/Caddyfile b/packaging/relay/Caddyfile new file mode 100644 index 0000000..ea368c8 --- /dev/null +++ b/packaging/relay/Caddyfile @@ -0,0 +1,15 @@ +odoh-relay.example.com { + handle /relay { + reverse_proxy numa-relay:8443 + } + handle /health { + reverse_proxy numa-relay:8443 + } + respond 404 + + # Per-request access logs defeat the point of an oblivious relay. + # Aggregate counters are exposed at /health on the relay itself. + log { + output discard + } +} diff --git a/packaging/relay/README.md b/packaging/relay/README.md new file mode 100644 index 0000000..373b263 --- /dev/null +++ b/packaging/relay/README.md @@ -0,0 +1,48 @@ +# Numa ODoH Relay — Docker deploy + +Two-container deploy: Caddy terminates TLS (auto-provisioning a Let's Encrypt +cert via ACME) and reverse-proxies to a Numa relay running on an internal +Docker network. The relay never reads sealed payloads; Caddy never logs them. + +## Prerequisites + +- A host with public 80/443 reachable from the internet. +- A DNS record (`A` or `AAAA`) pointing your chosen hostname at the host. +- Docker + Docker Compose v2. + +## Configure + +Edit `Caddyfile` and replace `odoh-relay.example.com` with your hostname. +That hostname is what ACME validates against and what ODoH clients will +configure as their relay URL: `https:///relay`. + +## Deploy + +```sh +docker compose up -d +docker compose logs -f caddy # watch ACME provisioning +``` + +First boot takes a few seconds while Caddy obtains the cert. Subsequent +restarts reuse the cached cert from the `caddy_data` volume. + +## Verify + +```sh +curl https:///health +# ok +# total 0 +# forwarded_ok 0 +# forwarded_err 0 +# rejected_bad_request 0 +``` + +Then point any ODoH client at `https:///relay` and watch the +counters tick. + +## Listing on the public ecosystem + +DNSCrypt's [v3/odoh-relays.md](https://github.com/DNSCrypt/dnscrypt-resolvers/blob/master/v3/odoh-relays.md) +is the canonical list. The pruned 2025-09-16 commit shows one public ODoH +relay survived the cull — running this compose file doubles global supply. +Open a PR there once your relay has been up for ~24 hours. diff --git a/packaging/relay/docker-compose.yml b/packaging/relay/docker-compose.yml new file mode 100644 index 0000000..9561535 --- /dev/null +++ b/packaging/relay/docker-compose.yml @@ -0,0 +1,26 @@ +services: + numa-relay: + image: ghcr.io/razvandimescu/numa:latest + command: ["relay", "8443", "0.0.0.0"] + restart: unless-stopped + networks: [internal] + + caddy: + image: caddy:2 + ports: + - "80:80" + - "443:443" + volumes: + - ./Caddyfile:/etc/caddy/Caddyfile:ro + - caddy_data:/data + - caddy_config:/config + restart: unless-stopped + depends_on: [numa-relay] + networks: [internal] + +networks: + internal: + +volumes: + caddy_data: + caddy_config: