Link a remote network to a demOS Stack over WireGuard, from one container on a Linux host.
117
Links a remote network to a demOS Stack. Run it on one Linux host on your network, and hosts on that network can reach the Stack's Ops and Private networks, and the Stack can reach them, with their real addresses in both directions.
Your network demOS Stack
+--------------------------------------+ +-------------------+
| | | |
| LAN hosts --> Linux host running | | |
| demos-wg-site --------+-------+-> Ops network |
| (route Ops, Private and | UDP | Private network |
| the resolver to this host) | 443 | Resolver |
+--------------------------------------+ +-------------------+
You need three things: the config file from the Stack, this container, and a route on your network.
sudo sysctl -w net.ipv4.ip_forward=1, persisted in /etc/sysctl.d/.1.1.1.1 or 8.8.8.8 (or the servers you set in EndpointResolvers), used to find the Stack.Docker Desktop on Mac or Windows won't work. Its host network is a VM's network, so nothing on your LAN can route through it. To connect a single laptop, use the Stack's VPN profile instead.
In demOS, open the Stack's Overview, choose Site link, and click Download config. You get
demos-<stack>.conf. It holds this site's private key, so lock it down and don't commit or share it:
sudo chown root:root demos-<stack>.conf && sudo chmod 600 demos-<stack>.conf
Run the command the Site link panel shows, from the folder holding the config. It looks like this:
docker run -d --name demos-<stack> --restart unless-stopped \
--network host \
--cap-drop ALL --cap-add NET_ADMIN --cap-add NET_RAW \
--security-opt no-new-privileges --read-only --tmpfs /run \
-v "$PWD/demos-<stack>.conf:/config/site.conf:ro" \
fduplex/demos-wg-site:1.0
Route the Stack's Ops and Private networks and its resolver address (all three shown in the Site link panel) from your LAN to this host. Either make this host the LAN's default gateway, or add a static route for each on your LAN router.
If your hosts need to reach Stack hosts by name, forward DNS for the Stack's domain (shown in the panel) to the resolver address. Your DNS server reaches the resolver through the route in step 3. The container itself always looks up the Stack through public DNS, so this forward can't stop it reconnecting while the tunnel is down.
docker logs demos-<stack> prints Connected once the tunnel is up, and the Site link panel turns green.
services:
site-link:
image: fduplex/demos-wg-site:1.0
container_name: demos-<stack>
restart: unless-stopped
network_mode: host
cap_drop: [ALL]
cap_add: [NET_ADMIN, NET_RAW]
security_opt: [no-new-privileges:true]
read_only: true
tmpfs: [/run]
volumes:
- ./demos-<stack>.conf:/config/site.conf:ro
The container runs with only the two capabilities it needs (NET_ADMIN to manage the interface and firewall rules,
NET_RAW for hosts that still use legacy iptables) and a read-only filesystem.
wgsite-<id>) with routes for the Stack's networks. It stops instead if this host
already routes any of them some other way.Run one container per Stack. One host can link to several Stacks as long as their networks don't overlap.
By default Stack hosts see your hosts' real addresses. This is what network sensors want, and it needs the route in step 3.
With -e SiteNat=on, traffic the Stack starts toward your site network is masqueraded to this host's address, so it
works without adding routes to your LAN router. Traffic your hosts start toward the Stack still needs the route. Stack
hosts then see this host's address instead of the real ones.
| Variable | Default | Meaning |
|---|---|---|
SiteNat | off | on masquerades Stack traffic toward your site network to this host's address |
SiteMss | 1380 | TCP MSS clamp on the tunnel |
ResolveInterval | 30 | Seconds between checks of the Stack's address |
HandshakeTimeout | 180 | Seconds without a handshake before the container reports unhealthy |
EndpointResolvers | 1.1.1.1 8.8.8.8 | Public DNS servers used, in order, to find the Stack's address |
docker logs demos-<stack> # Connected, or Waiting for the Stack
docker exec demos-<stack> wg-site status # handshake, transfer, routes
docker ps # healthy once a handshake is fresh
IP forwarding is off: run the sysctl line in Requirements and restart the container.Unable to create a WireGuard interface: the host kernel has no WireGuard module, the container is missing
--network host or --cap-add NET_ADMIN, or Docker is running rootless.This host already has a route for: another interface on this host uses one of the Stack's networks. Remove
that route, or run the site link on a different host.Waiting for the Stack: check that the Stack is on, that outbound UDP 443 is allowed, and that it
leaves from the site egress IP the Stack expects. The panel shows a warning when the tunnel connects from a
different address.firewalld, allow forwarding between your LAN interface and the wgsite-<id> interface; its rules are separate
and still apply.docker stop demos-<stack> && docker rm demos-<stack>
docker stop gives the container time to remove the tunnel and its rules. docker rm -f kills it before it can, and
leaves the tunnel up until the host reboots. If that happens, clean up by running it once with down:
docker run --rm --network host --cap-add NET_ADMIN -v "$PWD/demos-<stack>.conf:/config/site.conf:ro" \
fduplex/demos-wg-site:1.0 down
Every release is signed with cosign. Save this public
key as cosign.pub:
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE9ByYnXVgcLfEMXQeC6FZ1+5iVThX
Lkd7bd4rUXZdT3hAFK4+trzaEB36rJtbJoFxPozI845busdPBV+fuxesdQ==
-----END PUBLIC KEY-----
Then check the image before running it:
cosign verify --key cosign.pub fduplex/demos-wg-site:1.0
Copyright 2026 Full Duplex Media, LLC. Licensed under the Apache License, Version 2.0.
Content type
Image
Digest
sha256:42260a917…
Size
253 Bytes
Last updated
1 day ago
docker pull fduplex/demos-wg-site:sha256-05f02de03bbe5a5c86df883e44ab68b313719ad0dd4bc58c7c87a682746c0bda.sig