Repository navigation
Sidecar Mode
With LOCAL_GATEWAY=<exit> the container's own traffic — and therefore every container started with network_mode: service:wireguard — leaves through that exit, and an output kill switch (policy drop) makes sure nothing leaves any other way. This is the role nordvpn-wg and openconnect-client play, with a generic WireGuard upstream (a provider conf, your VPS, or a peer config from another instance of this image) as the tunnel. Inbound peers on wg0 are optional in this mode.
services:
wireguard:
image: azinchen/wireguard:latest
cap_add: [NET_ADMIN]
sysctls: [net.ipv4.ip_forward=1]
environment:
- LOCAL_GATEWAY=mullvad # upstreams/mullvad.conf
- NETWORK=192.168.1.0/24 # LAN stays reachable off-tunnel (optional)
volumes:
- ./volumes/config:/etc/wireguard
torrent:
image: ...
network_mode: service:wireguard # shares the tunnel and the kill switchContainers with their own network namespace, or LAN hosts, can be routed too:
- LOCAL_GATEWAY=mullvad
- FORWARD_FROM=auto # every subnet on the Docker network, or explicit CIDRsFORWARD_FROM clients point their default route at this container's address and get the same guard as a peer mapped to that gateway.
- Every WireGuard socket the image creates carries the fwmark
WG_FWMARK(0xca6c). Two policy rules steer locally generated traffic:fwmark 0xca6c lookup main(priority 700, in front of every source rule) keeps WireGuard's own encrypted UDP leaving viaeth0even when the container's address lies insideFORWARD_FROM, andiif lo lookup <table>(priority 1100) sends everything else the container originates into the exit's routing table — wg-quick's mechanism, done explicitly. (A singleiif lo not fwmarkrule would not do: the kernel inverts the whole rule, so forwarded traffic would match it too.) Destinations inNETWORKandFORWARD_FROMare pinned to the main table at priority 800, so the container's own replies to LAN hosts and to its clients never enter the tunnel. -
table inet wg_gwgains output and input chains with policy drop: accepted arelo, the tunnels, established flows, WireGuard's fwmarked UDP, DNS to the resolvers Docker configured (bootstrap, see below), neighbour discovery, theLISTEN_PORT/exit-node ports inbound, andNETWORK/NETWORK6. -
/etc/resolv.confis rewritten toDNS(default1.1.1.1,1.0.0.1), reached through the tunnel. Docker's embedded resolver would forward queries via the host, outside the tunnel. Hostnames in upstreamEndpoints are resolved through the resolvers Docker gave the container (saved before the rewrite), because that has to work before any tunnel exists; this is the only DNS that leaves off-tunnel. - Unmapped
wg0peers are pinned to the main table explicitly, so the local rule does not swallow them.
| Variable | Default | |
|---|---|---|
LOCAL_GATEWAY |
direct |
exit for local traffic: an exit node, an upstream, or a VPN_GATEWAYS name |
FORWARD_FROM / FORWARD_FROM6
|
– | CIDRs (or auto) routed through LOCAL_GATEWAY
|
GATEWAY_NAT6 |
true |
NAT66 for those clients |
NETWORK / NETWORK6
|
– | LAN CIDRs kept reachable off-tunnel |
DNS |
1.1.1.1,1.0.0.1 |
resolvers for resolv.conf
|
WG_FWMARK |
0xca6c |
fwmark on WireGuard sockets (change only on a collision) |
LOCAL_GATEWAY_RULE_PRIO |
1100 |
priority of the local rule |
WG_MARK_RULE_PRIO |
700 |
priority of the rule keeping WireGuard's fwmarked UDP on the main table (must be below every source rule) |
GATEWAY_DNS interception (as in the sibling images) is not implemented yet and is refused if set.
docker exec wireguard curl -s https://1.1.1.1/cdn-cgi/trace | grep ^ip= # the upstream's exit IP
docker exec -e HEALTH_CHECK_ENABLED=true -e HEALTH_CHECK_EGRESS=all wireguard healthcheck
docker exec wireguard nft list chain inet wg_gw outputStop the upstream and the fetch fails instead of showing your real address.
wireguard · MIT License · Built on WireGuard + s6-overlay
Setup
Routing
Operating
Under the hood