Running WireGuard inside Docker provides process isolation while preserving the VPN protocol’s performance benefits. This guide covers network namespace isolation, DNS leak prevention, kill switch implementation, and ensuring only designated containers route through the tunnel.
Why Run WireGuard in Docker?
Running WireGuard as a container offers several advantages. First, you gain process isolation — WireGuard runs in its own container environment, separated from your host system. Second, Docker’s networking capabilities make it easy to route specific traffic through the VPN tunnel. Third, containerization simplifies deployment and migration across different systems.
Network namespace isolation takes this further by providing complete network stack separation. When combined with Docker, you can create highly secure and flexible VPN configurations that prevent traffic from other processes from accidentally bypassing the tunnel.
Compared to a native host install, the containerized approach makes it easier to tear down and rebuild your VPN configuration, run multiple WireGuard instances with different endpoints, and audit which processes have tunnel access.
Prerequisites
Before starting, ensure you have Docker installed and the WireGuard kernel module available on your host:
# Check if WireGuard module is loaded
lsmod | grep wireguard
# Load the module if not present
sudo modprobe wireguard
# Install wireguard-tools if needed
sudo apt-get install wireguard-tools # Debian/Ubuntu
sudo pacman -S wireguard-tools # Arch Linux
sudo dnf install wireguard-tools # Fedora
Your WireGuard config file (wg0.conf) should be ready before proceeding, with [Interface] and [Peer] sections containing your private key, server address, and allowed IPs.
Basic WireGuard Container Setup
The simplest approach uses the linuxserver/wireguard image with the necessary capabilities:
docker run -d \
--name wireguard \
--cap-add NET_ADMIN \
--cap-add SYS_MODULE \
-e PUID=1000 \
-e PGID=1000 \
-e TZ=America/New_York \
-v /path/to/wg0.conf:/config/wg0.conf:ro \
-v /lib/modules:/lib/modules:ro \
--sysctl net.ipv4.conf.all.src_valid_mark=1 \
--network host \
linuxserver/wireguard
This configuration maps your WireGuard configuration file into the container and grants the necessary capabilities for network interface management. Always mount the configuration read-only (ro) to prevent the container from modifying your keys.
The --network host mode places the container on the host’s network stack. This is the simplest configuration but provides less isolation than the namespace approach below.
Network Namespace Isolation
For stronger isolation, create a dedicated network namespace. This approach separates the WireGuard interface from the host’s default network namespace, giving you precise control over which processes use the VPN tunnel.
Step 1: Create the Network Namespace
# Create a new network namespace
sudo ip netns add wg-vpn
# Verify the namespace exists
ip netns list
# Add a loopback interface to the namespace
sudo ip netns exec wg-vpn ip link set lo up
Step 2: Configure the WireGuard Interface in the Namespace
Rather than running the container directly inside the namespace, create the WireGuard interface manually in the namespace and attach it:
# Create the WireGuard interface
sudo ip link add wg0 type wireguard
# Move it to the namespace
sudo ip link set wg0 netns wg-vpn
# Apply your WireGuard configuration inside the namespace
sudo ip netns exec wg-vpn wg setconf wg0 /path/to/wg0.conf
sudo ip netns exec wg-vpn ip addr add 10.0.0.2/24 dev wg0
sudo ip netns exec wg-vpn ip link set wg0 up
Step 3: Configure Routing in the Namespace
Route all traffic in the namespace through the WireGuard tunnel:
# Add a default route through the WireGuard peer
sudo ip netns exec wg-vpn ip route add default dev wg0
# Verify routing
sudo ip netns exec wg-vpn ip route show
Step 4: Run Docker Container in the Namespace
Execute containers within the network namespace:
# Run a container bound to the wg-vpn namespace
sudo ip netns exec wg-vpn docker run -d \
--name protected-service \
--network host \
nginx:alpine
Any container launched inside the wg-vpn namespace can only reach the internet through the WireGuard tunnel. If the VPN drops, connectivity drops — there is no fallback to the unencrypted host interface.
Routing Specific Traffic Through the VPN
Rather than routing all traffic through the VPN, you can selectively tunnel specific containers. This approach works well for protecting specific services while maintaining direct access for others.
Create a Docker network that shares the WireGuard container’s network stack:
# Launch the WireGuard container first
docker run -d \
--name wireguard \
--cap-add NET_ADMIN \
--cap-add SYS_MODULE \
-v /path/to/wg0.conf:/config/wg0.conf:ro \
-v /lib/modules:/lib/modules:ro \
--sysctl net.ipv4.conf.all.src_valid_mark=1 \
linuxserver/wireguard
# Run a container sharing the WireGuard network stack
docker run -d \
--name protected-app \
--network container:wireguard \
nginx:alpine
The --network container:wireguard option shares the network stack of the WireGuard container. All traffic from protected-app passes through the WireGuard interface, including DNS queries.
Using Docker Compose for Simplified Management
Define your setup in a Docker Compose file for easier management:
version: '3.8'
services:
wireguard:
image: linuxserver/wireguard
container_name: wireguard
cap_add:
- NET_ADMIN
- SYS_MODULE
environment:
- PUID=1000
- PGID=1000
- TZ=America/New_York
volumes:
- ./wg0.conf:/config/wg0.conf:ro
- /lib/modules:/lib/modules:ro
sysctls:
- net.ipv4.conf.all.src_valid_mark=1
networks:
- wg-net
restart: unless-stopped
protected-app:
image: nginx:alpine
container_name: protected-app
network_mode: "service:wireguard"
depends_on:
- wireguard
restart: unless-stopped
networks:
wg-net:
driver: bridge
Using network_mode: "service:wireguard" in Compose is equivalent to --network container:wireguard in the CLI. The protected-app container inherits the WireGuard container’s entire network stack — including its IP address and routing table.
Preventing DNS Leaks
DNS leaks are a critical privacy concern in containerized VPN setups. When a container uses the system resolver or Docker’s embedded DNS, queries may bypass the tunnel. Configure DNS explicitly in your WireGuard peer section:
[Interface]
PrivateKey = YOUR_PRIVATE_KEY
Address = 10.0.0.2/24
DNS = 10.0.0.1
MTU = 1420
[Peer]
PublicKey = SERVER_PUBLIC_KEY
Endpoint = vpn.example.com:51820
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25
Setting AllowedIPs = 0.0.0.0/0, ::/0 routes all IPv4 and IPv6 traffic through the tunnel. The DNS field tells WireGuard to configure the container’s resolver to use the VPN server’s DNS, preventing queries from leaking to your ISP.
Verify DNS is not leaking after setup:
docker exec protected-app nslookup whoami.cloudflare.com 1.1.1.1
If the result returns the VPN server’s IP rather than your real IP, DNS routes correctly through the tunnel.
Troubleshooting Common Issues
Packet Loss and MTU Issues
WireGuard often requires MTU adjustments when running in containers due to the overhead added by the WireGuard encapsulation header (60 bytes for IPv4, 80 for IPv6). Add this to your [Interface] section:
[Interface]
MTU = 1420
If you still experience fragmentation issues, lower the MTU further (try 1380) and verify with:
docker exec wireguard ping -M do -s 1380 8.8.8.8
The -M do flag prevents fragmentation. If ping fails at 1380 but succeeds at lower sizes, reduce your MTU accordingly.
Container Connectivity
If your container cannot reach the internet, verify the WireGuard interface is up and the peer is connected:
# Check WireGuard status in the container
docker exec wireguard wg show
# Check container connectivity
docker exec wireguard ping -c 3 1.1.1.1
# Verify IP routing
docker exec wireguard ip route show
A healthy wg show output includes the peer’s last handshake timestamp (within the last 3 minutes for active connections) and transferred bytes incrementing.
Kill Switch Implementation
A kill switch prevents traffic from bypassing the VPN if the WireGuard connection drops. Implement it using iptables inside the container:
# Allow only WireGuard traffic on the physical interface
docker exec wireguard iptables -I OUTPUT -o eth0 -j DROP
docker exec wireguard iptables -I OUTPUT -o eth0 -p udp --dport 51820 -j ACCEPT
# Allow all traffic on the WireGuard interface
docker exec wireguard iptables -I OUTPUT -o wg0 -j ACCEPT
These rules block all outgoing traffic on the physical interface except the WireGuard UDP handshake, while allowing all traffic over the WireGuard interface. If the tunnel drops, no traffic escapes.
Security Considerations
Running WireGuard in Docker adds security layers but requires attention to several details.
Always mount your configuration read-only to prevent accidental modification. Store private keys outside the Docker image — pass them via Docker secrets, not baked into images. Avoid running containers with --privileged mode; the NET_ADMIN and SYS_MODULE capabilities are sufficient and grant far fewer host permissions.
Regularly update your WireGuard image to receive security patches:
docker pull linuxserver/wireguard:latest
docker compose up -d
Container logs can reveal sensitive information including IP addresses and connection metadata. Ensure log aggregation systems handle this data with appropriate access controls.
Frequently Asked Questions
Can I run multiple WireGuard tunnels simultaneously in Docker? Yes. Run multiple WireGuard containers with different interface names (wg0, wg1, wg2) and different configuration files. Use the container network sharing approach to route different applications through different tunnels.
Does this setup work on macOS with Docker Desktop?
Partially. Docker Desktop on macOS runs containers inside a Linux VM. WireGuard kernel module support depends on the VM’s kernel. The userspace WireGuard implementation (wireguard-go) works cross-platform but has lower throughput than the kernel implementation.
How do I verify all traffic routes through the VPN?
From inside the protected container, run curl ifconfig.me and compare the returned IP against your VPN server’s IP. If they match, all traffic routes through the tunnel. Also run curl ifconfig.me --interface eth0 — if this returns your real IP, the kill switch is not configured.
What is the performance overhead of running WireGuard in Docker? Container overhead is minimal — typically under 1% throughput reduction compared to a native WireGuard install. The dominant factor is WireGuard’s cryptographic overhead (ChaCha20-Poly1305), which is the same whether containerized or native.
Related Reading
- WireGuard Key Rotation Best Practices
- WireGuard DNS Configuration Options Explained
- How to Set Up VLAN Isolation for IoT Devices on Home Network
Built by theluckystrike — More at zovo.one