- Shell 100%
| unmodified_configs | ||
| .gitignore | ||
| compose.yml | ||
| example.env | ||
| README.md | ||
| setup_wireguard.sh | ||
WireGuard Thin-Client Gateway (Docker)
This project runs a WireGuard client inside Docker and exposes a remote LAN behind this device to your WireGuard network.
It uses the official LinuxServer.io WireGuard image and a simple compose.yml with IP
forwarding enabled. Image version pinning is recommended (see
Environment variables section).
Use case example:
- Your WireGuard server is at
10.252.2.1/24 - This thin client connects as
10.252.2.6/32 - The thin client offers access to
192.168.100.0/24(LAN behind it)
Example topology (logical)
Mobile laptop (peer)
10.252.2.20/32
│
│ WireGuard tunnel
│
┌───────────────┴───────────────┐
│ WireGuard server (WG) │
│ 10.252.2.1/24 │
└───────────────┬───────────────┘
│
│ WireGuard tunnel
│
┌───────────────┴───────────────┐
│ Thin client gateway (Docker) │
│ 10.252.2.6/32 (wg0) │
└───────────────┬───────────────┘
│
│ NAT + forwarding (PostUp/PostDown)
│
┌─────────────┴─────────────┐
│ Remote LAN │
│ 192.168.100.0/24 │
└─────────────┬─────────────┘
│
LAN devices (e.g. printer, NAS)
Requirements
- Docker + Docker Compose (plugin version, not legacy
docker-compose) - Linux host (Debian in this example)
- A working WireGuard server configuration
tput(usually pre-installed, used for colored script output)
Included
compose.ymlwith:NET_ADMINcapability- kernel modules mounted
- forwarding enabled
- Support for image version pinning via
IMAGE_TAGenvironment variable - Network readiness checks via
network_waitservice - Automatic restart on failure via
deunhealthservice
setup_wireguard.shhelper script for automated configuration setupexample.envtemplate for environment variable configuration- WireGuard client configs stored inside
./wireguard/wg_confs/(generated after first container start) - Automatic NAT and forwarding rules using
PostUp/PostDown
Directory Layout
project/
├─ compose.yml
├─ .env (optional)
├── example.env
├─ README.md
├─ setup_wireguard.sh
├─ unmodified_configs/
│ └─ wg_<NAME>.conf
└─ wireguard/
└─ wg_confs/
└─ wg_<NAME>.conf
Setup using helper script (recommended)
The included setup_wireguard.sh script automates the configuration process:
-
Place your unmodified WireGuard client config in
./unmodified_configs/- The filename must follow:
wg_<NAME>.conf(e.g.,wg_foo.conf) - Only letters and numbers after
wg_until.conf
- The filename must follow:
-
Run the setup script:
./setup_wireguard.shThe script will:
- Check for required dependencies (docker, docker compose, tput)
- Ensure the
./wireguard/wg_confs/directory exists (starts container temporarily if needed) - Process all
.conffiles from./unmodified_configs/ - Automatically add
PostUpandPostDownrules to the[Interface]section - Move processed configs to
./wireguard/wg_confs/ - Remove original files from
./unmodified_configs/after successful processing - Automatically start or restart the container
Note: The script requires docker compose (plugin version), not the legacy
docker-compose standalone.
Manual setup
Prepare a client config
Copy your WireGuard client config into ./wireguard/wg_confs/ (generated after first
container start)
The filename must follow: wg_<NAME>.conf
Allowed:
wg_foo.confwg_bar.confwg_123.conf
Not allowed:
wg_foo_bar.conf← underscore after second word will not be detectedwg-foo.confwgMyClient.conf
Rule: after wg_ only letters + numbers until .conf
Modify the config (important!)
Before placing the file, update its [Interface] section to include the generic
PostUp/PostDown rules used for gateway mode:
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o e+ -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o e+ -j MASQUERADE
Why this is required:
%iis replaced with the WireGuard interface (example:wg0)- NAT allows remote peers to reach the LAN behind the thin-client
e+matches network interfaces likeenp3s0,ens3,eth0, etc.
Example client config:
[Interface]
Address = 10.252.2.6/32
PrivateKey = <SECRET>
MTU = 1350
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o e+ -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o e+ -j MASQUERADE
[Peer]
PublicKey = <SERVER_PUBLIC_KEY>
PresharedKey = <PSK>
AllowedIPs = 10.252.2.0/24
Endpoint = example.com:51821
PersistentKeepalive = 15
Notice: we do not add the LAN network here. The LAN network belongs in the server's peer config (AllowedIPs).
Server configuration reminder
On the server, configure the peer like this:
[Peer]
PublicKey = <CLIENT_PUBLIC_KEY>
PresharedKey = <PSK>
# add the clients LAN network here
# |
# v
# WG-IP , LAN Network
AllowedIPs = 10.252.2.6/32,192.168.100.0/24
PersistentKeepalive = 15
Optional: Environment variables
You can customize the container behavior using environment variables in an .env file.
Image version pinning (recommended)
It is strongly recommended to pin the WireGuard image version to ensure reproducible deployments and avoid unexpected updates.
-
Create an
.envfile (or copyexample.envto.env):IMAGE_TAG=1.0.20250521 -
Find available tags at Docker Hub
-
The image will be:
lscr.io/linuxserver/wireguard:${IMAGE_TAG}- With
IMAGE_TAG=1.0.20250521: uses the specific version - Without
.envfile: defaults tolatest(not recommended for production)
- With
Custom container name
You can customize the container name by setting the NET_NAME environment variable.
This is useful when running multiple instances on the same host.
-
Add to your
.envfile:NET_NAME=my_home_network -
The container name will be:
wireguard_provider_${NET_NAME}- With
NET_NAME=my_home_network: container name iswireguard_provider_my_home_network - Without
.envfile: container name defaults towireguard_provider_docker_compose
- With
Note: Docker Compose automatically loads variables from .env file if present. You
can combine both variables in the same .env file:
NET_NAME=my_home_network
IMAGE_TAG=1.0.20250521
Start container
docker compose up -d
Check logs:
# Default container name
docker logs -f wireguard_provider_docker_compose
# Or if using NET_NAME (replace with your actual container name)
docker logs -f wireguard_provider_<NET_NAME>
Services Overview
The compose.yml includes three services that work together to ensure reliable WireGuard
connectivity:
1. network_wait Service
Image: busybox:latest
Purpose: Ensures the host network and DNS are fully operational before starting WireGuard.
Why we use it:
- On system boot, Docker containers may start before the host network is ready
- WireGuard needs DNS resolution to connect to servers (e.g.,
eample.com:51820) - Without DNS, WireGuard fails with "Try again" errors and "Configuration parsing error"
- This service waits for both network connectivity (ping) and DNS resolution before allowing WireGuard to start
How it works:
- Continuously checks network connectivity by pinging
8.8.8.8or1.1.1.1 - Verifies DNS resolution by resolving
google.com - Only becomes "healthy" when both conditions are met
- WireGuard depends on this service being healthy before starting
2. wireguard_provider Service
Image: lscr.io/linuxserver/wireguard
Purpose: The main WireGuard client container that establishes the VPN tunnel.
Why we use it:
- Provides a containerized WireGuard client with automatic configuration management
- Supports multiple tunnel configurations
- Handles iptables rules for NAT and forwarding automatically
Health checks:
- Verifies WireGuard interface exists and is configured
- Checks for active tunnel connection (latest handshake)
- Monitors general network connectivity
- If health checks fail, the container is marked as unhealthy
3. deunhealth Service
Image: qmcgaw/deunhealth
Purpose: Automatically restarts unhealthy containers.
Why we use it:
- Docker's default behavior does not restart containers just because they're unhealthy
- When WireGuard fails (e.g., DNS unavailable, server unreachable), the container stays running but unhealthy
deunhealthmonitors containers with thedeunhealth.restart.on.unhealthy=truelabel- When it detects an unhealthy container, it automatically restarts it
- This ensures WireGuard keeps retrying until network conditions improve
How it works:
- Runs with
network_mode: none(no network access needed) - Monitors Docker socket for container health status
- Restarts containers labeled with
deunhealth.restart.on.unhealthy=true - Lightweight and efficient monitoring solution
Networking notes
NET_ADMINis required to run iptables inside the containerNATis enabled only for traffic exiting your Linux host uplink- Docker sysctls enable IPv4 forwarding automatically
- DNS servers (
8.8.8.8,8.8.4.4,1.1.1.1) are explicitly configured to ensure reliable DNS resolution
Troubleshooting
Check routes & interface
ip route ip addr
Check NAT table
iptables -t nat -L -n
Check filter rules
iptables -L -n
Based on
LinuxServer.io WireGuard documentation: their image, capabilities, and volume layout:
License
GNU General Public License v3.0 or later.
Credits
- LinuxServer.io for the WireGuard image
- LinuxServer.io WireGuard documentation
- BusyBox for the lightweight network wait service
- deunhealth by qmcgaw for automatic unhealthy container restart functionality