Files
ara/traefik-infrastructure/logic/solution/architecture.md
T

7.6 KiB

Solution Architecture

Overview

The wylab.me infrastructure resolves the Traefik + Technitium + Docker circular dependency through four interacting components. The architecture separates the DNS resolution path for internal services (via Technitium at 172.17.0.1) from the ACME certificate DNS path (via Cloudflare 1.1.1.1 directly), eliminating both the 8-second latency and the circular dependency.


Component Graph

                    INTERNET
                       │
                  Let's Encrypt
                  ACME endpoint
                       │
                       │ (DNS via 1.1.1.1 — bypasses Technitium)
                       │
    ┌──────────────────────────────────────────────┐
    │              UNRAID HOST (192.168.1.50)       │
    │                                               │
    │  ┌─────────────────────────────────────────┐ │
    │  │  HOST NETWORK NAMESPACE                 │ │
    │  │                                         │ │
    │  │  Technitium DNS                         │ │
    │  │  Binds: 0.0.0.0:53 (UDP/TCP)           │ │
    │  │  → Reachable at 172.17.0.1:53          │ │
    │  │  → Reachable at 192.168.1.50:53        │ │
    │  │                                         │ │
    │  │  iptables DNAT rules (for host-net      │ │
    │  │  containers needing bridge services)    │ │
    │  └─────────────────────────────────────────┘ │
    │                                               │
    │       docker0: 172.17.0.1/16                  │
    │              │                                │
    │  ┌───────────┴────────────────────────────┐  │
    │  │         BRIDGE NETWORK                  │  │
    │  │                                         │  │
    │  │  Traefik Container (bridge mode)        │  │
    │  │  IP: 172.17.0.x                         │  │
    │  │  Port 80, 443 mapped                    │  │
    │  │  → DNS: 172.17.0.1 (daemon.json)       │  │
    │  │  → ACME resolvers: ["1.1.1.1:53"]      │  │
    │  │  → Backend: http://172.17.0.1:PORT      │  │
    │  │                                         │  │
    │  │  Other Service Containers (bridge mode) │  │
    │  │  IPs: 172.17.0.x                        │  │
    │  │  → DNS: 172.17.0.1 (daemon.json)       │  │
    │  └─────────────────────────────────────────┘  │
    │                                               │
    │  /etc/docker/daemon.json:                     │
    │    {"dns": ["172.17.0.1"]}                    │
    │                                               │
    │  /boot/config/go:                             │
    │    (persists daemon.json + iptables on boot)  │
    └──────────────────────────────────────────────┘

Components

Traefik Reverse Proxy

  • Purpose: Terminates TLS, routes HTTP/HTTPS traffic to backend containers, manages Let's Encrypt certificates
  • Network mode: Bridge (default Docker network)
  • Inputs: Inbound HTTP (port 80) and HTTPS (port 443) from LAN/internet; Docker label-based service discovery; Traefik dynamic config files
  • Outputs: Proxied requests to backend containers; ACME certificate requests to Let's Encrypt; certificate storage in acme.json
  • Key design choices:
    • Runs in bridge mode (not host mode) to use Docker's internal service discovery and label-based routing
    • ACME resolver configured with resolvers = ["1.1.1.1:53"] to bypass Technitium for certificate DNS
    • Backend URLs for host-networked services use 172.17.0.1:PORT (not 127.0.0.1:PORT or 192.168.1.50:PORT)

Technitium DNS Server

  • Purpose: Internal DNS resolver for wylab.me domain; forwards public DNS queries upstream
  • Network mode: Host (shares host network namespace)
  • Inputs: DNS queries on UDP/TCP port 53 from all network interfaces
  • Outputs: DNS responses; upstream forwarding for non-local domains
  • Key design choices:
    • Host network mode required so it binds to 172.17.0.1 (the docker0 gateway), making it reachable from bridge containers
    • Management UI (dns.wylab.me) is served through Traefik — creates the documented circular dependency for ACME, mitigated by Traefik's explicit ACME DNS bypass
    • Also reachable from LAN at 192.168.1.50:53 for non-Docker clients

Docker Daemon (daemon.json)

  • Purpose: Provides DNS configuration to all bridge-networked containers at creation time
  • Location: /etc/docker/daemon.json on Unraid host
  • Inputs: none (static configuration)
  • Outputs: Injects nameserver 172.17.0.1 into /etc/resolv.conf of all new bridge containers
  • Key design choices:
    • Single field {"dns": ["172.17.0.1"]} — minimal change, system-wide effect
    • Requires Docker daemon restart; applies to all subsequently created containers
    • Must be persisted in /boot/config/go (Unraid wipes /etc/ on reboot)

iptables DNAT Rules

  • Purpose: Enables host-networked containers (which don't have bridge gateway) to reach bridge-networked services if needed; routes inbound traffic correctly
  • Location: Applied at host level; persisted via /boot/config/go
  • Inputs: Packets from host-networked containers destined for bridge container IPs
  • Outputs: Rewritten destination IPs for correct routing
  • Key design choices:
    • Required only for host-networked containers that need to reach bridge containers (inverse of the main DNS problem)
    • Must be re-applied on each boot via /boot/config/go

/boot/config/go (Persistence Layer)

  • Purpose: Ensures all runtime configuration survives Unraid reboots
  • Location: /boot/config/go on the Unraid flash drive
  • Inputs: none (startup script)
  • Outputs: Writes daemon.json, applies iptables rules, restarts Docker daemon if needed
  • Key design choices:
    • This is the canonical Unraid persistence mechanism — no alternative survives reboots
    • Script must be idempotent (safe to run multiple times in case of partial failures)
    • Order matters: daemon.json must be written before Docker daemon starts (or before daemon restart)

Interaction Flows

Flow 1: Normal DNS resolution (bridge container → internal service)

Container → /etc/resolv.conf (nameserver 172.17.0.1) → Technitium at 172.17.0.1:53 → Internal DNS response (~2ms)

Flow 2: ACME certificate renewal (Traefik → Let's Encrypt)

Traefik ACME module → resolvers=["1.1.1.1:53"] → Cloudflare DNS → Resolves acme-v02.api.letsencrypt.org → Let's Encrypt HTTPS endpoint → Certificate

Note: Technitium is NOT in this path. The circular dependency is broken.

Flow 3: Host-networked container needing bridge service (via iptables DNAT)

Host-net container → iptables DNAT rule → Rewritten to bridge container IP → Bridge service

Flow 4: Boot sequence

Unraid starts → /boot/config/go executes → daemon.json written → iptables rules applied → Docker daemon (re)started → Containers start with correct DNS