Sign inSign up

fduplex/demos-wg-site

By fduplex

•Updated 1 day ago

Link a remote network to a demOS Stack over WireGuard, from one container on a Linux host.

Image
Networking
1

117

fduplex/demos-wg-site repository overview

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.

⁠Requirements

  • A Linux host on the network you're linking, running Docker as root (rootless Docker can't create the interface). The site router itself works, or any always-on machine.
  • A kernel with WireGuard built in (Linux 5.6 or later; every current distribution).
  • IP forwarding on: sudo sysctl -w net.ipv4.ip_forward=1, persisted in /etc/sysctl.d/.
  • Outbound UDP 443 to the Stack, leaving from the site egress IP you gave the Stack when you created it.
  • Outbound DNS to 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.

⁠Quick start

  1. 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
    
  2. 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
    
  3. 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.

  4. 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.

⁠Docker Compose

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.

⁠What it does

  • Creates a WireGuard interface (wgsite-<id>) with routes for the Stack's networks. It stops instead if this host already routes any of them some other way.
  • Forwards only between your site network and the Stack's networks. Anything else arriving from the tunnel, or headed into it, is dropped, so the Stack can't reach other networks this host routes to.
  • Clamps TCP MSS to 1380 so full-size packets fit the tunnel's 1420-byte MTU.
  • Follows the Stack's address. If it changes, for example after the Stack is turned off and on, the tunnel moves to the new address on the next check (every 30 seconds by default, looked up through public DNS), so there's nothing to restart.
  • Removes the interface and every rule it added when the container stops.

Run one container per Stack. One host can link to several Stacks as long as their networks don't overlap.

⁠Routing: real addresses or NAT

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.

⁠Settings

VariableDefaultMeaning
SiteNatoffon masquerades Stack traffic toward your site network to this host's address
SiteMss1380TCP MSS clamp on the tunnel
ResolveInterval30Seconds between checks of the Stack's address
HandshakeTimeout180Seconds without a handshake before the container reports unhealthy
EndpointResolvers1.1.1.1 8.8.8.8Public DNS servers used, in order, to find the Stack's address

⁠Checking it

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

⁠Troubleshooting

  • 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.
  • Stuck on 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.
  • The tunnel is up but LAN hosts can't reach the Stack: check the LAN route (step 3). If this host runs firewalld, allow forwarding between your LAN interface and the wgsite-<id> interface; its rules are separate and still apply.
  • Large transfers stall: something between your hosts and this one is dropping fragments. The MSS clamp covers TCP. For tools that send large UDP packets, such as packet capture forwarders, cap their packet size at 1380 bytes.

⁠Stopping

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

⁠Verifying the image

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

⁠License

Copyright 2026 Full Duplex Media, LLC. Licensed under the Apache License, Version 2.0⁠.

Tag summary

Content type

Image

Digest

sha256:42260a917…

Size

253 Bytes

Last updated

1 day ago

docker pull fduplex/demos-wg-site:sha256-05f02de03bbe5a5c86df883e44ab68b313719ad0dd4bc58c7c87a682746c0bda.sig