Affiliate disclosure — This guide links to Contabo, the VPS we use for our own self-hosted WireGuard. If you order through our link we earn a commission at no extra cost to you. We only document what we actually run.
Running WireGuard in Docker gets you the thing that makes containers worth it: a server you can stand up, tear down, version, and move to another host in minutes, with zero packages installed on the machine itself. The catch is that a VPN container is not a normal container — it touches the kernel's networking stack, so a few flags are non-negotiable. Get those right and a containerised WireGuard is every bit as fast as a bare-metal one. Get them wrong and the tunnel connects but silently routes nothing.
This is the clean, production-shaped way to do it.
First, the one thing people get wrong about "WireGuard in Docker"
WireGuard is a kernel module. When you "run WireGuard in Docker", the encryption and routing still happen in the host's Linux kernel — the container only carries the userspace tools (wg, wg-quick) and your configuration. That is why there is no performance penalty: the data path never enters the container.
It also explains the requirements. To create and manage a network interface from inside a container, that container needs elevated networking capability (NET_ADMIN), and the host must be allowed to forward packets (net.ipv4.ip_forward=1). These are not optional hardening choices — without them the container either refuses to start or comes up dead.
Choose your image: linuxserver/wireguard vs wg-easy
Two images cover almost every use case:
linuxserver/wireguard— a clean, config-driven server. You define peers via environment variables or by editing files in a mounted/configdirectory. It generates client configs and QR codes on first run. Best when you want infrastructure-as-code, many peers, or to commit your setup to a private repo.wg-easy— WireGuard plus a web dashboard. Create and revoke peers from a browser, scan QR codes for phones, watch live transfer stats per client. Best for a handful of devices and fast onboarding without touching the CLI.
Both are mature and widely run. The decision is dashboard-vs-files, not quality.
The docker-compose file (wg-easy)
services:
wg-easy:
image: ghcr.io/wg-easy/wg-easy:latest
container_name: wg-easy
environment:
- WG_HOST=vpn.example.com # your VPS domain or public IP
- PASSWORD_HASH=<bcrypt-hash> # web UI login
- WG_DEFAULT_DNS=1.1.1.1
volumes:
- ./config:/etc/wireguard # persistence — keep this on the host
ports:
- "51820:51820/udp" # WireGuard data
- "51821:51821/tcp" # web UI (firewall this to your IP)
cap_add:
- NET_ADMIN
- SYS_MODULE
sysctls:
- net.ipv4.ip_forward=1
- net.ipv4.conf.all.src_valid_mark=1
restart: unless-stopped
Three lines do the heavy lifting and are the usual failure points:
cap_add: NET_ADMINlets the container manage thewg0interface. Without it the container can't bring the tunnel up.sysctls: net.ipv4.ip_forward=1turns the box into a router. Without it the tunnel connects but no traffic crosses it — the single most common "it says connected but nothing works" cause.volumes: ./configpersists keys and peers on the host so a recreate doesn't wipe every client.
Expose the UDP data port publicly; firewall the TCP web-UI port to your own IP — never leave 51821 open to the internet.
The host checklist (do this before you blame the container)
A container can be perfect and still route nothing because of the layer underneath it:
- Cloud firewall: open
51820/udpin your provider's security group, not just on the host. This is the number-one silent failure on a VPS. - Host kernel module: most modern distros ship the WireGuard module in-kernel (Linux 5.6+). If
modprobe wireguardfails on the host, install the kernel headers — the container can't load a module the host doesn't have. - DNS for clients: set
WG_DEFAULT_DNSso clients don't leak queries to whatever resolver they were using. Pair it with our WireGuard DNS-leak prevention guide.
When a container is the right call — and when it isn't
Use Docker when you run other services on the same box and want WireGuard isolated and reproducible, when you want wg-easy's dashboard, or when you redeploy across hosts often. The whole setup becomes one compose file you can version and move.
Skip Docker when the VPS does nothing but WireGuard. A bare-metal apt install wireguard plus a config file is simpler, has one less moving part, and is trivially scriptable — see our WireGuard config templates and the full Contabo + WireGuard self-host walkthrough. For a single-purpose VPN box, the container adds packaging you don't need.
The VPS under it
Containerised or not, WireGuard needs a host with a public IP and a decent uplink. A small VPS is plenty — WireGuard is light enough that a 4–6 €/month instance saturates its network port long before the CPU notices. We run ours on Contabo for the price-to-bandwidth ratio:
Get a Contabo VPS for your WireGuard container →
For the full price/performance breakdown across providers, see the cheapest VPS for a WireGuard VPN.
The honest limitation
Docker makes WireGuard portable, not more secure. The container shares the host kernel, runs with NET_ADMIN, and is only as locked-down as the host. It does not sandbox WireGuard from the machine — anyone with root on the host owns the tunnel. Treat the container as deployment convenience, harden the host as if WireGuard were installed directly on it, and keep the web UI port off the public internet. Do that, and containerised WireGuard is the cleanest self-host setup there is.
★ Nuremberg GDPR datacenter · ✓ Dedicated IPv4 included · 200+ Mbps guaranteed
Self-host your VPN on your own VPS → ContaboFull root access · public IPv4 · pick your region→