Hint: This is IPv4 only. Remove all IPv6 stuff from the wireguard-config!
Current entrypoint.sh assumptions for strict mode (egress only through wg0 + killswitch):
- Use a literal IPv4
Endpoint(Endpoint = x.x.x.x:51820), not a hostname. - Use a single peer endpoint for this gateway container.
- Entrypoint:
container/entrypoint.sh - Dockerfile:
container/Dockerfile
podman build --pull --no-cache --tag localhost/wg-gateway-image -f container/Dockerfile .This is IPv4 only. Remove all IPv6 settings from your WireGuard config before proceeding.
Note: If your setup supports it, you can use --userns=auto instead of explicit --uidmap/--gidmap for simpler user namespace isolation.
Pasta provides excellent network isolation for rootless containers.
podman run --name wg-gateway \
--detach \
--replace \
--pull never \
--cap-drop ALL \
--cap-add NET_ADMIN \
--ipc=none \
--security-opt no-new-privileges:true \
--network pasta:--no-map-gw,-t,none,-u,none,-T,none,-U,none,--no-tcp,--no-icmp,--no-dhcp,--no-dhcpv6,--no-ndp,--no-ra,-4,-D,none,-S,none \
--sysctl net.ipv4.conf.all.src_valid_mark=1 \
--sysctl net.ipv6.conf.all.disable_ipv6=1 \
--sysctl net.ipv6.conf.default.disable_ipv6=1 \
--uidmap 0:10000:64 \
--gidmap 0:10000:64 \
--volume "$PWD"/wg0.conf:/etc/wireguard/wg0.conf:ro \
localhost/wg-gateway-imageRootful mode requires an isolated bridge network to prevent unintended host access.
Create the network first:
podman network create \
--subnet 172.20.0.0/16 \
--disable-dns \
--opt isolate=true \
wg-gateway-netThen run the container:
podman run --name wg-gateway \
--detach \
--replace \
--pull never \
--cap-drop ALL \
--cap-add NET_ADMIN \
--ipc=none \
--network wg-gateway-net \
--security-opt no-new-privileges:true \
--sysctl net.ipv4.conf.all.src_valid_mark=1 \
--sysctl net.ipv6.conf.all.disable_ipv6=1 \
--sysctl net.ipv6.conf.default.disable_ipv6=1 \
--uidmap 0:100000:64 \
--gidmap 0:100000:64 \
--volume "$PWD"/wg0.conf:/etc/wireguard/wg0.conf:ro \
localhost/wg-gateway-imageRun a simple test. This should return the IP of your VPN.
podman run --rm --net=container:wg-gateway docker.io/curlimages/curl -skL --max-time 2 icanhazip.comRemove the container and image when you are done:
podman rm -f wg-gateway
podman rmi -f localhost/wg-gateway-imageIf you created the rootful network, remove it as well:
podman network rm -f wg-gateway-netThe following options can be combined freely. Add them to your podman run command as needed.
Restrict the container to a predefined set of allowed syscalls:
--security-opt seccomp=/usr/share/containers/seccomp.jsonPrevent resource exhaustion by capping memory and process count:
--pids-limit 10 \
--memory 10m \
--memory-swap 10mMount the container's root filesystem as read-only:
--read-onlyIf the container needs a writable temp directory, add a locked-down tmpfs:
--tmpfs /tmp:rw,noexec,nosuid,size=16M--dns on --net=container:... is not supported by Podman - that's why we need workarounds.
Option 1 - Via WireGuard config (entrypoint.sh writes DNS from the [Interface] block):
echo -n > resolv.conf && chmod 666 resolv.confchmod 666 is required so the container can write /etc/resolv.conf even with user namespace mappings.
--volume "$PWD"/resolv.conf:/etc/resolv.confOption 2 - Manual static DNS (set servers explicitly, skip config-based setup):
--dns 1.1.1.1 \
--dns 8.8.8.8 \
--dns 9.9.9.9 \
--env SKIP_DNS=1entrypoint.sh treats SKIP_DNS=1 and SKIP_DNS=TRUE as enabled.
Override DNS for a single sidecar container:
podman run --rm \
--net=container:wg-gateway \
--volume "$PWD"/resolv.conf:/etc/resolv.conf:U,ro \
docker.io/curlimages/curl -skL --max-time 2 icanhazip.com