Run the VPN on a Docker gateway
A gateway run with Docker carries VPN sessions the same way an appliance does, as long as its host gives it what it needs. Gateways on Kubernetes, Docker Desktop, rootless Docker, and Podman can’t run the VPN yet.
What the host needs
Section titled “What the host needs”- The container run as Witina shows it. The commands under Gateways → Add gateway →
Run it as a container use the host’s network and the
NET_ADMIN,NET_RAW, andNET_BIND_SERVICEcapabilities. The VPN needs the host’s network andNET_ADMIN. - WireGuard in the host’s kernel. Current Linux distributions have it. We test on Ubuntu 24.04, Debian 12, and Rocky Linux 9.
- IP forwarding on. Docker turns it on itself. With Docker’s nftables firewall
backend, Docker asks you to turn it on (
net.ipv4.ip_forward = 1). - The VPN’s addresses free. Sessions use
100.64.0.0/16for tunnels and100.96.0.0/12for aliases. A host that already has addresses or routes in those ranges, or runs Tailscale, can’t run the VPN.
If something is missing, the gateway’s VPN tab says what, and so does the site’s Gateways list.
What the gateway changes on the host
Section titled “What the gateway changes on the host”While the VPN is on at the gateway’s site, the gateway keeps:
- a WireGuard interface,
wtn0, and a route to the tunnel addresses; - an nftables table,
inet witina_vpn, that holds each session to its routes and sends its traffic out from the host’s address; - two rules in Docker’s
DOCKER-USERchain, so Docker’s firewall lets the VPN’s traffic through; - where the host’s firewall filters incoming traffic, as ufw does, two rules that let technicians’ DNS queries reach the gateway.
Its iptables rules all carry the comment witina-vpn.
Technicians reach the site’s network through the host, but not the host itself or the host’s other containers: the gateway forwards their traffic only out of interfaces that lead to a network, never into Docker’s bridges.
Turning the VPN off at the site removes all of this. One kernel setting stays on until
the host restarts: connection byte counting (net.netfilter.nf_conntrack_acct), which
flow records need.
Hosts running firewalld
Section titled “Hosts running firewalld”firewalld, the default firewall on Red Hat Enterprise Linux, Rocky Linux, and AlmaLinux,
doesn’t let other programs change its rules. Until wtn0 is in one of its zones,
firewalld turns away technicians’ DNS queries to the gateway, so names in the site’s
domains don’t resolve. The gateway’s VPN tab shows a warning with the commands. Run
them once on the host:
sudo firewall-cmd --permanent --zone=trusted --add-interface=wtn0sudo firewall-cmd --reloadThe trusted zone only stops firewalld dropping what arrives on wtn0. The gateway’s own
rules still let technicians reach nothing on the host except its DNS. The warning goes
away within a minute.
Restarts and updates
Section titled “Restarts and updates”Restarting the container, or the gateway updating itself, doesn’t drop anyone. The kernel keeps the tunnels up while the gateway restarts, and the new gateway process takes them over. A session’s traffic stops when the session ends, even if the gateway isn’t running.
Removing the gateway
Section titled “Removing the gateway”Removing the container leaves the interface, the table, and the rules in place, for the same reason: the host can’t tell a removal from a restart. On a host that’s done with Witina, restart the host, or run:
sudo ip link delete wtn0sudo nft delete table inet witina_vpnsudo ip route delete local 100.64.0.0/24 dev lo table localfor chain in DOCKER-USER INPUT; do sudo iptables -S "$chain" | grep -- '--comment witina-vpn' | sed 's/^-A /-D /' | while read -r rule; do sudo iptables $rule; donedone