Repository navigation
Architecture and Internals
| Layer | What |
|---|---|
| Base | Alpine Linux, pinned apk versions |
| Init / supervisor | s6-overlay |
| VPN | WireGuard: the kernel module through wg (wireguard-tools-wg), wireguard-go as fallback |
| Firewall / routing | nftables, iproute2 policy routing. No wg-quick. |
-
s6-fetch— downloads the architecture-appropriate s6-overlay tarballs. -
rootfs— assemblesroot/(services, scripts) + s6; normalizes permissions; stamps the image version intobackend-functions. -
Final — Alpine + runtime packages, one
COPYof the rootfs,ENTRYPOINT ["/init"],S6_BEHAVIOUR_IF_STAGE2_FAILS=2so a failed init oneshot aborts the container (fail-closed).
init-env ─► init-nat ─► init-upstreams ─► init-vpngw ─► svc-wireguard (longrun)
svc-peer-watch · svc-vpngw-watch · svc-vpngw-gw-resolve (idle unless enabled)
| Service | Does |
|---|---|
| init-env | validates every variable; IPv6 availability; generates the server keypair; parses PEERS + peers.conf into normalized records (/run/wireguard/peers.def), generating keys and allocating/persisting addresses and exit-node ports; writes device configs + QR; in sidecar mode saves Docker's resolv.conf and rewrites it to DNS. |
| init-nat | forwarding sysctls; table inet wg: forward chain (policy drop; wg0 → WAN, wg0 → wg-*, wg0 → gateway egress interfaces, return traffic, wg0 → wg0, sidecar clients) and masquerade (VPN_SUBNET on the WAN and gateway interfaces, everything entering a wg-* tunnel); inet wg_mss (MSS clamp); inet wg_ttl (TTL_SET). |
| init-upstreams | brings up wg-<name> for every exit node (config generated from the record) and every UPSTREAMS_DIR/*.conf (sanitised); resolves Endpoint hostnames; records each interface's families in /run/wireguard/ifaces/. |
| init-vpngw | named gateways → routing tables (default via <ip> or default dev wg-<name>); peer map; subnet default; LOCAL_GATEWAY rule; kill switch inet wg_gw; per-peer policy rules and set membership. State in /run/wireguard/vpngw/. |
| svc-wireguard | creates wg0 (ip link add + wg setconf), adds the server address and allowed= routes, then blocks; SIGHUP → wg-reload; finish removes wg0. |
| watchers |
inotifyd on the peers file / map file (PEERS_WATCH, VPN_PEER_GATEWAY_WATCH); the resolver loop: scheduled re-resolution of gateways and endpoints (VPN_GATEWAYS_RESOLVE_INTERVAL), or, without it, the always-on endpoint watch that re-resolves a dial-out endpoint once its handshake is older than UPSTREAM_HANDSHAKE_TIMEOUT. |
wg-quick owns the default route (its fwmark + suppress_prefixlength trick), edits resolv.conf, runs hooks from the conf and needs bash. A node with several tunnels and policy routing needs none of that and must not have a provider conf take over its default route. The image uses ip + wg setconf to create interfaces and wg syncconf to reload peers without touching unchanged ones; the single piece of wg-quick behaviour it keeps — fwmarking WireGuard's own sockets so they are never routed into a tunnel — is explicit (WG_FWMARK).
Source policy routing keyed on peer addresses (static, so installed at boot and reconciled on reload — no connect hook as in ocserv-server):
- rule prio
800:to <cidr> lookup mainforVPN_SUBNET,IPV6_SUBNET(when routed) and everyallowed=network — peer-to-peer traffic never enters a gateway table - rule prio
900: onefrom <cidr> lookup <table>per address of a mapped peer (its/32and everyallowed=network);direct→main - rule prio
1000:from VPN_SUBNET lookup 100for the subnet default (orlookup mainin sidecar mode) - rule prio
700:fwmark 0xca6c lookup main— WireGuard's own encrypted UDP always uses the main table, ahead of every source rule (WG_MARK_RULE_PRIO) - rule prio
1100:iif lo lookup <table>forLOCAL_GATEWAY - tables
101, 102, …: one per named gateway,default via <next-hop>ordefault dev wg-<name>
Kill switch inet wg_gw (forward hook, priority −10): per-gateway guards (next-hop guard or interface guard), direct/block sets, the subnet default guard; in sidecar mode also output/input chains with policy drop.
| Path | Role |
|---|---|
/etc/wireguard/ (volume) |
server/ keys, peers.conf, peers/<name>/ (keys, address, device config, QR), upstreams/, optional map files |
/run/wireguard/ |
generated interface configs (wg0.conf, wg-<name>.conf), peers.def / peers.applied (normalized records), ifaces/ (tunnel capabilities, endpoints), vpngw/ (gateways, subnet, local, applied rules) |
/usr/local/bin/ |
backend-functions (shared env + helpers), wg-reload, peer-conf, peer-report, network-diagnostic, healthcheck, vpngw-gw-resolve
|
VPN_GATEWAY*, VPN_GATEWAYS*, MSS, TTL_SET, IPV6_*, HEALTH_CHECK_*, FORWARD_FROM*, NETWORK*, DNS keep the semantics they have in ocserv-server, openconnect-client and nordvpn-wg; VPN_USER_* becomes VPN_PEER_*. TTL_INC and GATEWAY_DNS are reserved and refused.
wireguard · MIT License · Built on WireGuard + s6-overlay
Setup
Routing
Operating
Under the hood