# ADR-0008 — VPN egress model: Gluetun sidecar (default) + Cilium Egress Gateway - Status: **Superseded by [ADR-0012](adr-0012-vpn-egress-single-expressvpn-gateway.md)** (2026-08-15) - Date: 2026-08-15 - Serves: R-NET-05, R-NET-06, R-NET-07 - Supersedes: — > **Superseded.** OQ-3 resolved to ExpressVPN + a **single shared in-cluster tunnel** with rules > enforced outside the service. The Gluetun-sidecar-per-pod default below is no longer the model; see > [ADR-0012](adr-0012-vpn-egress-single-expressvpn-gateway.md). Retained for history. ## Context A subset of services (sonarr, radarr, jackett, qbittorrent) must egress **only** through a commercial VPN, with a **kill-switch** guaranteeing no leak to the plain internet if the tunnel drops. Egress class must be a declarative service property (`egress: vpn`) that doesn't force a workload rewrite. ## Decision Adopt a **two-pattern** model behind the single `egress: vpn` contract, with **Gluetun sidecar as the default** for the initial catalog: - **Default — Pattern A: Gluetun sidecar per pod.** A Gluetun container holds the WireGuard/OpenVPN tunnel; the app shares its network namespace. Gluetun's built-in **firewall kill-switch** blocks all non-tunnel egress by default — satisfying R-NET-06 intrinsically and per-service. - **Available — Pattern B: central VPN egress gateway + Cilium Egress Gateway.** One (HA) gateway holds the tunnel; Cilium Egress Gateway steers `vpn`-labeled pods through it, with policy denying any fallback to the internet path. Use when tunnel count must be minimized. The service-facing contract is identical for both: declare `egress: vpn`; the platform wires the chosen pattern. Kill-switch behavior is a hard acceptance gate for both. ## Alternatives considered - **Central gateway only** — fewer tunnels but a shared failure domain and more complex kill-switch guarantees across the fabric; kept as Pattern B, not the default. - **Node-level policy routing / per-node VPN** — couples egress to node identity, harder to reason about per-service; rejected as the primary model. - **App-native VPN config** — rejected: not declarative, not uniform, easy to misconfigure and leak. ## Consequences - Per-service isolation and an intrinsic kill-switch out of the box (Pattern A). - More sidecars/tunnels at scale (Pattern A) — Pattern B is the escape hatch if the VPN provider limits concurrent tunnels/egress IPs (OQ-3). - Kill-switch must be **fault-tested** (tunnel forced down ⇒ zero internet egress) as a standing acceptance gate, not a one-time check ([03](../docs/03-networking-isolation-and-egress.md), R-NET-06). - VPN credentials are SOPS-encrypted (ADR-0007). WireGuard preferred over OpenVPN where supported. ## Open questions - **OQ-3** — VPN provider/protocol and concurrent-tunnel/egress-IP limits (drives Pattern A vs B and gateway HA).