# RackNerd VPN hub — OpenVPN (road warriors) + Duo Auth Proxy + Unbound. # # All services use network_mode: host on purpose: # * openvpn's tun0 must live in the host netns so the kernel can route # tun0 <-> wg0 directly (wg0 is host-level wg-quick). # * unbound/duo bind ONLY loopback + tunnel IPs (enforced in their own # configs), so host networking exposes nothing on the public IP. # Nothing here publishes ports; the host firewall (host/rules.v4.template) # is the single source of truth for what is reachable from where. name: racknerd-vpnhub services: unbound: build: ./unbound container_name: unbound network_mode: host env_file: .env volumes: - ./unbound/unbound.conf.template:/etc/unbound/unbound.conf.template:ro restart: unless-stopped healthcheck: test: ["CMD", "dig", "@127.0.0.1", "+time=3", "+tries=1", "cloudflare.com"] interval: 30s timeout: 5s retries: 3 duo-authproxy: build: ./duo-authproxy container_name: duo-authproxy network_mode: host # RADIUS listener bound to 127.0.0.1:1812 only env_file: .env volumes: # AD LDAPS issuing-CA chain, exported from your CA (see certs/README.md) - ./duo-authproxy/certs:/certs:ro restart: unless-stopped openvpn: build: ./openvpn container_name: openvpn network_mode: host # creates tun0 in the host netns cap_add: - NET_ADMIN devices: - /dev/net/tun env_file: .env volumes: - ./openvpn/server.conf:/etc/openvpn/server.conf:ro - ./openvpn/pki:/etc/openvpn/pki:ro - openvpn-state:/var/lib/openvpn depends_on: - duo-authproxy - unbound restart: unless-stopped volumes: openvpn-state: