WireGuard in Podman Rootless Containers

Kernel-mode WireGuard can run neatly inside rootless Podman containers. This article will show you how, with 10 different examples of running Podman rootlessly (plus 3 that must be run as root):

Differences From Docker

But first, let’s go over a few important differences between running WireGuard in a “rootfull” Docker container and “rootless” Podman container:

Kernel Module Loading

Like with Docker, the Podman host must have the WireGuard kernel module installed. All Linux kernels since version 5.6 come with the WireGuard module built-in, so you don’t have to install anything unless you’re running a Linux kernel older than 5.6 on the host. If you are running an older kernel, you may be able to install the WireGuard kernel module from your distro’s package manager; but more often you will need to compile the WireGuard kernel module from source.

Unlike with Docker, however, with Podman you must load the WireGuard kernel module outside of Podman. Loading kernel modules requires root access; when you run WireGuard as root on the host, the kernel module is loaded for you automatically. But when you run WireGuard in a Podman container, if the module hasn’t already been loaded, WireGuard will fail to start up (usually with a Protocol not supported error message).

You can check which kernel modules have been loaded by running the lsmod command on the host. If the WireGuard module has been loaded, the result will look something like this:

$ lsmod | grep wireguard
wireguard              94208  0
libblake2s             16384  1 wireguard
ip6_udp_tunnel         16384  1 wireguard
udp_tunnel             28672  1 wireguard
libcurve25519_generic  40960  1 wireguard

If the WireGuard module hasn’t been loaded yet, however, it won’t be listed. In that case, you can use the modprobe command to load it:

$ lsmod | grep wireguard
$ sudo modprobe wireguard
$ lsmod | grep wireguard
wireguard              94208  0
...

Kernel modules only need to be loaded once after every boot, so you could just run modprobe manually every time you boot up the Podman host. However, on hosts with systemd, you can instead list out the modules you want loaded in a file in the host’s /etc/modules-load.d/ directory (the file can be named anything you want; we’ll use wireguard.conf for our example):

$ echo wireguard | sudo tee /etc/modules-load.d/wireguard.conf
$ sudo systemctl restart systemd-modules-load
Note

On hosts without systemd, the /etc/modules file usually serves the same purpose as the /etc/modules-load.d/ directory.

Also, if you are going to run iptables inside a Podman container, as we will do with several of the following examples, you will need to load several iptables kernel modules the same way. The following /etc/modules-load.d/wireguard.conf file will ensure that the WireGuard kernel module and all the iptables modules we use in this article’s various examples (except for the examples which take over the default route) are loaded at boot:

# /etc/modules-load.d/wireguard.conf
# WireGuard module
wireguard
# iptables modules for basic DNAT/SNAT and masquerading
iptable_nat
xt_MASQUERADE
xt_nat

For the examples which take over the default route (ie use AllowedIPs = 0.0.0.0/0), the WireGuard script will run several iptables commands behind the scenes — which require you to load several different iptables modules. The following /etc/modules-load.d/wireguard.conf file will ensure that you have all the iptables modules required to take over the default route:

# /etc/modules-load.d/wireguard.conf
# WireGuard module
wireguard
# iptables modules for wg-quick default route
iptable_mangle
iptable_raw
xt_addrtype
xt_comment
xt_connmark
xt_mark

Update 2023-12-27

With the latest container images, built on Alpine Linux 3.19 (which uses the nftables kernel backend), you will need to load several different nftables kernel modules in place of the above iptables modules. The following /etc/modules-load.d/wireguard.conf file will load all the kernel modules needed for the new container images:

# /etc/modules-load.d/wireguard.conf
# WireGuard module
wireguard
# iptables/nftables modules for basic DNAT/SNAT and masquerading
nft_chain_nat
nft_compat
xt_nat
# nftables modules for wg-quick default route
nft_ct
nft_fib_inet

After modifying this file, run the sudo systemctl restart systemd-modules-load command to make sure all the modules you included in the file are loaded. If this command results in an error message, run journalctl -u systemd-modules-load to check which modules failed to load.

Note

In older versions of the Linux kernel, the xt_MASQUERADE module was named differently (for example, with RHEL 8 kernels, the module was named ipt_MASQUERADE). You can check for the correct name of the module by running the following command:

$ find /lib/modules/$(uname -r)/kernel -iname '*masq*'
/lib/modules/4.18.0-305.3.1.el8_4.aarch64/kernel/net/ipv4/netfilter/ipt_MASQUERADE.ko.xz
/lib/modules/4.18.0-305.3.1.el8_4.aarch64/kernel/net/ipv6/netfilter/ip6t_MASQUERADE.ko.xz
/lib/modules/4.18.0-305.3.1.el8_4.aarch64/kernel/net/netfilter/nft_masq.ko.xz

In the above example, the (IPv4) module’s name is ipt_MASQUERADE, so you would use that name in place of xt_MASQUERADE.

Firewall Modifications

Another way that (rootless) Podman differs from (rootfull) Docker is that when Docker runs a container, it will automatically set up the iptables rules needed for the container’s network.

Modifying the firewall requires root access, however, so rootless Podman does not do this. If you’re running a firewall on the host that blocks inbound connections by default (as you should), and you need to initiate WireGuard connections from a remote host, you will need to manually open up the WireGuard listen port for the Podman container.

For example, if you’re using firewalld to manage your firewall, and firewalld’s public zone is protecting the network interface from which remote WireGuard connections will be made (eg eth0), and your Podman container uses a WireGuard listen port of 51820, you’d need to run the following command to allow access to that WireGuard port:

$ sudo firewall-cmd --zone=public --add-port=51820/udp
success
Tip

Be sure to run firewall-cmd --runtime-to-permanent after making changes to your firewalld configuration to persist those changes beyond a system reboot.

Volume Mount Options

When running Podman on a system with SELinux enabled (which is the default for Fedora, RHEL, and many of their derivatives), certain host volumes mounted to a Podman container will need to be relabeled with a SELinux label that allows the container to access the volume. (This is true with Docker, too, but you’re more likely to run across it with Podman, as Podman is often used in place of Docker by the same distros that enable SELinux by default.)

All the examples in this article will use the Z option when mounting the WireGuard config directory from the host into the container, which directs Podman to automatically re-label the volume with a private, unshared label that allows the container to read and write to the volume under SELinux:

podman run \
    ...
    --volume /srv/wg-hub/conf:/etc/wireguard​:Z \
    docker.io/procustodibus/wireguard

Unqualified Search Registries

Docker automatically uses docker.io as its default registry when searching for container images; Podman has no default registry. If you try to run an image with an unqualified name with Podman, you’ll get an error like the following:

Error: short-name "procustodibus/wireguard" did not resolve to an alias and no unqualified-search registries are defined in "/etc/containers/registries.conf"

To fix this, you can either add docker.io to the unqualified-search-registries list in your /etc/containers/registry.conf file:

# /etc/containers/registries.conf
unqualified-search-registries = ["docker.io"]

Or you can simply qualify the image name when running a container, as we will do in all the examples in this article:

podman run \
    ...
    docker.io/​procustodibus/wireguard

Network Mode With Podman Compose

Prior to version 1.0.4, Podman Compose mostly ignored the network_mode and networks directives in docker-compose.yml files; none of the docker-compose.yml examples in this article that use either directive will work correctly with Podman Compose prior to version 1.0.4.

Unfortunately, as of mid-October 2022, Podman Compose version 1.0.4 hasn’t yet been officially released. To access its fixes, you have to run the development version of it. You can install the development version with the following command:

$ pip3 install https://github.com/containers/podman-compose/archive/devel.tar.gz

Install it as root (eg with sudo) to make it available to all users on your system (instead of your own user only). If you don’t have pip on your system, you usually can install it via the python3-pip package of your distro’s package manager (and then run pip3 install --upgrade pip setuptools to upgrade pip to its latest version).

Kernel Parameter List With Podman Compose

Another quirk of Podman Compose is that it does not support using the sysctls directive with a map of parameters — but it does with a key=value list. So the following use of the sysctls directive in a docker-compose.yml file will not work:

sysctls:
  net.ipv4.conf.all.forwarding: 1

But this use of the sysctls directive with Podman Compose will work:

sysctls:
- net.ipv4.conf.all.forwarding=1

Forwarding Kernel Parameter

And one last significant way that rootless Podman differs from rootfull Docker is that Docker will automatically turn on the host’s net.ipv4.ip_forward kernel parameter (aka net.ipv4.conf.all.forwarding) whenever it starts up a container (unless that container is run without any network access at all). Rootless Podman does not do this, so in cases where you want WireGuard to forward packets, and you’ve not already turned on this parameter system-wide, you must explicitly turn it on for the WireGuard container.

You can do this via the --sysctl net.ipv4.conf.all.forwarding=1 flag with podman run, or via the above sysctls directive in a docker-compose.yml file. All the examples in this article which require this parameter will include the above flag or directive.

Example Scenarios

Now that we’ve covered the key differences between Podman and Docker when using it to run a WireGuard container, let’s look at some concrete examples of running WireGuard in a Podman container:

Use for Hub

WireGuard Hub in a Podman Container

The first example we’ll look at is running the hub of a WireGuard hub-and-spoke topology in a Podman container. We’ll use the same WireGuard configuration for this hub as “Host C” from the WireGuard Hub and Spoke Configuration guide — with the one exception that we won’t set the “IP forwarding” kernel parameter in the WireGuard configuration (we’ll set it instead in the Podman configuration).

Important

In order to run this example, make sure you load the WireGuard kernel module on the Podman host, and open up access to the host’s UDP port 51823, as described by the Kernel Module Loading and Firewall Modifications sections at the beginning of this article.

Save the WireGuard configuration for the hub in its own directory somewhere convenient on the host, like in the /srv/wg-hub/conf/ directory:

# /srv/wg-hub/conf/wg0.conf

# local settings for Host C
[Interface]
PrivateKey = CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCGA=
Address = 10.0.0.3/32
ListenPort = 51823

# remote settings for Endpoint A
[Peer]
PublicKey = /TOE4TKtAqVsePRVR+5AA43HkAK5DSntkOCO7nYq5xU=
AllowedIPs = 10.0.0.1/32

# remote settings for Endpoint B
[Peer]
PublicKey = fE/wdxzl0klVp/IR8UcaoGUMjqaWi3jAd7KzHKFS6Ds=
AllowedIPs = 10.0.0.2/32

Then you can run a container for the hub with the following podman run command:

podman run \
    --cap-add NET_ADMIN \
    --name wg-hub \
    --publish 51823:51823/udp \
    --rm \
    --sysctl net.ipv4.conf.all.forwarding=1 \
    --volume /srv/wg-hub/conf:/etc/wireguard:Z \
    docker.io/procustodibus/wireguard

These are what the command arguments do:

  1. --cap-add NET_ADMIN: Grants the container the NET_ADMIN capability — this is required to start up a WireGuard interface inside the container.

  2. --name wg-hub: Sets the container’s name to wg-hub (you can set this to whatever name you want, or omit it entirely if you don’t care how it’s named).

  3. --publish 51823:51823/udp: Forwards the host’s public 51823 UDP port to the container’s 51823 UDP port — make sure the latter matches the ListenPort setting in the WireGuard config file (the former can be whatever port you want to expose publicly). Also make sure you separately open up access through your firewall to the host’s public port if you want remote hosts to be able to initiate WireGuard connections to this container.

  4. --rm: Deletes the container when it’s shut down (you can omit this if you don’t want to delete the container).

  5. --sysctl net.ipv4.conf.all.forwarding=1: Enables packet forwarding in the container.

  6. --volume /srv/wg-hub/conf:/etc/wireguard:Z: Maps the /srv/wg-hub/conf/ directory on the host to the /etc/wireguard/ directory in the container (you can change the host directory to whatever you want). The Z option allows the container to access this directory even when SELinux is enabled.

  7. docker.io/procustodibus/wireguard: Runs the latest version of the WireGuard image.

Alternately, you can place the following docker-compose.yml file in the directory above the WireGuard configuration file:

# /srv/wg-hub/docker-compose.yml
version: '3'
services:
  wireguard:
    image: docker.io/procustodibus/wireguard
    cap_add:
    - NET_ADMIN
    ports:
    - 51823:51823/udp
    sysctls:
    - net.ipv4.conf.all.forwarding=1
    volumes:
    - ./conf:/etc/wireguard:Z

And then start up a container for the hub by running podman-compose up from the same directory as the docker-compose.yml file.

The Podman configuration for this example is very similar to the rootfull Docker Use for Hub example from the original WireGuard Containers article, with the following differences for Podman:

  1. We explicitly turned on the packet forwarding kernel parameter.

  2. We added the Z option to the WireGuard config volume.

  3. We qualified the image name with the docker.io registry.

Use for Site Masquerading

WireGuard Point-to-Site Masquerading From Docker Container

Next, let’s look at how to use a Podman container to provide connectivity to the Podman host’s LAN (Local Area Network) from other remote WireGuard endpoints, in a point-to-site topology (with masquerading). We’ll use the same WireGuard configuration for the container as “Host β” from the WireGuard Point to Site Configuration guide — with two exceptions:

  1. We won’t set the “IP forwarding” kernel parameter in the WireGuard configuration (we’ll set it instead in the Podman configuration).

  2. We can simplify the container’s iptables rules to a single line (to masquerade all forwarded packets except those sent out its WireGuard interface).

Important

In order to run this example, make sure you load the WireGuard and iptables kernel modules on the Podman host, and open up access to the host’s UDP port 51822, as described by the Kernel Module Loading and Firewall Modifications sections at the beginning of this article.

Save the WireGuard configuration for Host β in its own directory somewhere convenient on the host, like in the /srv/wg-p2s/conf/ directory:

# /srv/wg-p2s/conf/wg0.conf

# local settings for Host β
[Interface]
PrivateKey = ABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBFA=
Address = 10.0.0.2/32
ListenPort = 51822

# IP masquerading
PreUp = iptables -t nat -A POSTROUTING ! -o %i -j MASQUERADE

# remote settings for Endpoint A
[Peer]
PublicKey = /TOE4TKtAqVsePRVR+5AA43HkAK5DSntkOCO7nYq5xU=
AllowedIPs = 10.0.0.1/32

You can run a container for this WireGuard interface with the following podman run command:

podman run \
    --cap-add NET_ADMIN \
    --cap-add NET_RAW \
    --publish 51822:51822/udp \
    --name wg-p2s \
    --rm \
    --sysctl net.ipv4.conf.all.forwarding=1 \
    --volume /srv/wg-p2s/conf:/etc/wireguard:Z \
    docker.io/procustodibus/wireguard

Alternately, you can place the following docker-compose.yml file in the directory above the WireGuard configuration file:

# /srv/wg-p2s/docker-compose.yml
version: '3'
services:
  wireguard:
    image: docker.io/procustodibus/wireguard
    cap_add:
    - NET_ADMIN
    - NET_RAW
    ports:
    - 51822:51822/udp
    sysctls:
    - net.ipv4.conf.all.forwarding=1
    volumes:
    - ./conf:/etc/wireguard:Z

And then start up the container by running podman-compose up from the same directory as the docker-compose.yml file.

The Podman configuration for this example is very similar to the rootfull Docker Use for Site Masquerading example from the original WireGuard Containers article, with the following differences for Podman:

  1. We granted the NET_RAW capability (to run iptables in a rootless container).

  2. We explicitly turned on the packet forwarding kernel parameter.

  3. We added the Z option to the WireGuard config volume.

  4. We qualified the image name with the docker.io registry.

Use for Site Port Forwarding

WireGuard Point-to-Site Port Forwarding From Docker Container

You can also use a Podman container to provide connectivity from the Podman host’s LAN (Local Area Network) to other remote WireGuard endpoints, reversing the conventional point-to-site topology by forwarding traffic for specific services from the “site” to the “point”. We’ll use the same WireGuard configuration for the container in this scenario as “Host β” from the WireGuard Point to Site With Port Forwarding guide — with the one exception that we won’t set the “IP forwarding” kernel parameter in the WireGuard configuration (we’ll set it instead in the Podman configuration).

Important

In order to run this example, make sure you load the WireGuard and iptables kernel modules on the Podman host, and open up access to the host’s UDP port 51822, as described by the Kernel Module Loading and Firewall Modifications sections at the beginning of this article.

Save the WireGuard configuration for the site in its own directory somewhere convenient on the host, like in the /srv/wg-fwd/conf/ directory:

# /srv/wg-fwd/conf/wg0.conf

# local settings for Host β
[Interface]
PrivateKey = ABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBFA=
Address = 10.0.0.2/32
ListenPort = 51822

# port forwarding
PreUp = iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination 10.0.0.1

# remote settings for Endpoint A
[Peer]
PublicKey = /TOE4TKtAqVsePRVR+5AA43HkAK5DSntkOCO7nYq5xU=
AllowedIPs = 10.0.0.1/32

Because in this scenario we’re forwarding the host’s port 80, which is a “privileged” port (as are all ports below 1024), we’d normally need to use root on the host in order to run a container with this WireGuard configuration. We can still run it with rootless Podman, however, if we adjust this restriction. The simplest way to do this is to modify the definition of privileged ports; run the following command as root:

$ sudo sysctl -w net.ipv4.ip_unprivileged_port_start=0
Note

This only temporarily changes the privileged port definition — to permanently change it, add the above kernel parameter to some file in your /etc/sysctl.d/ directory (or to your /etc/sysctl.conf file).

Also see the answers to the Allow non-root process to bind to port 80 and 443? question on Super User for several alternative ways to run a rootless command that can listen on privileged ports.

Also, if you have a firewall set up on the host, you’ll need to adjust it to allow access to port 80 from the LAN. See the “Point to Site” section of the How to Use WireGuard with UFW guide, the How to Use WireGuard with Firewalld guide, or the How to Use WireGuard With Nftables guide (or the “Configure Firewall on Host β” section of the WireGuard Point to Site With Port Forwarding guide) for examples of how to do this.

Note

This is in addition to allowing access through your firewall to the WireGuard listen port (51822 in this example), as described in the Firewall Modifications section.

Then run the Podman container for this WireGuard configuration with the following podman run command:

podman run \
    --cap-add NET_ADMIN \
    --cap-add NET_RAW \
    --name wg-fwd \
    --network slirp4netns:port_handler=slirp4netns \
    --publish 80:80 \
    --publish 51822:51822/udp \
    --rm \
    --sysctl net.ipv4.conf.all.forwarding=1 \
    --volume /srv/wg-fwd/conf:/etc/wireguard:Z \
    docker.io/procustodibus/wireguard

Alternately, you can place the following docker-compose.yml file in the directory above the WireGuard configuration file:

# /srv/wg-fwd/docker-compose.yml
version: '3'
services:
  wireguard:
    image: docker.io/procustodibus/wireguard
    cap_add:
    - NET_ADMIN
    - NET_RAW
    network_mode: slirp4netns:port_handler=slirp4netns
    ports:
    - 80:80
    - 51822:51822/udp
    sysctls:
    - net.ipv4.conf.all.forwarding=1
    volumes:
    - ./conf:/etc/wireguard:Z

And then start up the container by running podman-compose up from the same directory as the docker-compose.yml file.

Note

This example requires Podman Compose version 1.0.4 or newer (see the Network Mode With Podman Compose section).

The Podman configuration for this example is very similar to the rootfull Docker Use for Site Port Forwarding example from the original WireGuard Containers article, with the following differences for Podman:

  1. We granted the NET_RAW capability (to run iptables in a rootless container).

  2. We set slirp4netns as the port-forwarding handler (to allow port-forwarding from the host to a destination outside of the container).

  3. We explicitly turned on the packet forwarding kernel parameter.

  4. We added the Z option to the WireGuard config volume.

  5. We qualified the image name with the docker.io registry.

Use for Inbound Port Forwarding

WireGuard Inbound Port Forwarding From Docker Container

You can also use port forwarding with a Podman container in a way that makes the Podman host a public proxy for some private service connected through a WireGuard point-to-point topology (or hub-and-spoke topology).

For this example, we’ll use a point-to-point network between Endpoint A and Endpoint B, similar to the one shown by the WireGuard Point to Point Configuration guide, but where instead of Endpoint A being an end-user workstation, it’s actually a server in some datacenter with a publicly-exposed TCP port 80. Endpoint B is a webserver in a different datacenter, with only its WireGuard UDP port 51822 exposed.

Important

In order to run this example, make sure you load the WireGuard and iptables kernel modules on the Podman host, as described by the Kernel Module Loading section at the beginning of this article.

We’ll add a couple of iptables rules to the WireGuard configuration for Endpoint A, but otherwise we’ll use the same configuration for it as in the WireGuard Point to Point Configuration guide. Save this configuration file in its own directory somewhere convenient on the host, like in the /srv/wg-ifw/conf/ directory:

# /srv/wg-ifw/conf/wg0.conf

# local settings for Endpoint A
[Interface]
PrivateKey = AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEE=
Address = 10.0.0.1/32
ListenPort = 51821

# port forwarding
PreUp = iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination 10.0.0.2
PreUp = iptables -t nat -A POSTROUTING -p tcp --dport 80 -j MASQUERADE

# remote settings for Endpoint B
[Peer]
PublicKey = fE/wdxzl0klVp/IR8UcaoGUMjqaWi3jAd7KzHKFS6Ds=
Endpoint = 203.0.113.2:51822
AllowedIPs = 10.0.0.2/32

The first iptables rule above will forward packets that the container receives at TCP port 80 on to Endpoint B. The second iptables rule will masquerade those packets as if they had originated from the container itself, so that Endpoint B will send responses to them back through Endpoint A.

Tip

You can omit the iptables masquerading rule if you instead use one of the several alternative routing strategies on Endpoint B covered in the WireGuard Port Forwarding From the Internet article.

Also, because in this scenario we’re forwarding the host’s port 80, which is a “privileged” port (as are all ports below 1024), we’d normally need to use root on the host in order to run a container with this WireGuard configuration. We can still run it with rootless Podman, however, if we adjust this restriction. The simplest way to do this is to modify the definition of privileged ports; run the following command as root:

$ sudo sysctl -w net.ipv4.ip_unprivileged_port_start=0
Note

This only temporarily changes the privileged port definition — to permanently change it, add the above kernel parameter to some file in your /etc/sysctl.d/ directory (or to your /etc/sysctl.conf file).

Also see the answers to the Allow non-root process to bind to port 80 and 443? question on Super User for several alternative ways to run a rootless command that can listen on privileged ports.

And also, if you have a firewall set up on the host, you’ll need to adjust it to allow access to port 80 from its public network interface. If you’re using firewalld to manage your firewall, you can probably simply add port 80 to its public zone:

$ sudo firewall-cmd --zone=public --add-port=80/tcp
success
Note

This is in addition to allowing access through your firewall to the WireGuard listen port (51822 in this example), as described in the Firewall Modifications section.

Then run a container for this WireGuard configuration with the following podman run command:

podman run \
    --cap-add NET_ADMIN \
    --cap-add NET_RAW \
    --publish 80:80 \
    --publish 51821:51821/udp \
    --name wg-ifw \
    --network slirp4netns:port_handler=slirp4netns \
    --rm \
    --sysctl net.ipv4.conf.all.forwarding=1 \
    --volume /srv/wg-ifw/conf:/etc/wireguard:Z \
    docker.io/procustodibus/wireguard

Alternately, you can place the following docker-compose.yml file in the directory above the WireGuard configuration file:

# /srv/wg-ifw/docker-compose.yml
version: '3'
services:
  wireguard:
    image: docker.io/procustodibus/wireguard
    cap_add:
    - NET_ADMIN
    - NET_RAW
    ports:
    - 80:80
    - 51821:51821/udp
    network_mode: slirp4netns:port_handler=slirp4netns
    sysctls:
    - net.ipv4.conf.all.forwarding=1
    volumes:
    - ./conf:/etc/wireguard:Z

And then start up the container by running podman-compose up from the same directory as the docker-compose.yml file.

Note

This example requires Podman Compose version 1.0.4 or newer (see the Network Mode With Podman Compose section).

The Podman configuration for this example is very similar to the rootfull Docker Use for Inbound Port Forwarding example from the original WireGuard Containers article, with the following differences for Podman:

  1. We granted the NET_RAW capability (to run iptables in a rootless container).

  2. We set slirp4netns as the port-forwarding handler (to allow port-forwarding from the host to a destination outside of the container).

  3. We explicitly turned on the packet forwarding kernel parameter.

  4. We added the Z option to the WireGuard config volume.

  5. We qualified the image name with the docker.io registry.

Use for Outbound Port Forwarding

WireGuard Outbound Port Forwarding From Docker Container

You can also use port forwarding with a Podman container in a way that makes the Podman host a private proxy for some public service connected through a WireGuard point-to-point topology (or hub-and-spoke topology).

For this example, we’ll again use a point-to-point network between Endpoint A and Endpoint B, similar to the one shown by the WireGuard Point to Point Configuration guide, but where instead of Endpoint B hosting a webserver itself, it merely forwards traffic tunneled to it on TCP port 80 to some other external webserver.

Important

In order to run this example, make sure you load the WireGuard and iptables kernel modules on the Podman host, and open up access to the host’s UDP port 51822, as described by the Kernel Module Loading and Firewall Modifications sections at the beginning of this article.

We’ll add a couple of iptables rules to the WireGuard configuration for Endpoint B, but otherwise we’ll use the same configuration for it as in the WireGuard Point to Point Configuration guide. Save this configuration file in its own directory somewhere convenient on the host, like in the /srv/wg-ofw/conf/ directory:

# /srv/wg-ofw/conf/wg0.conf
# local settings for Endpoint B
[Interface]
PrivateKey = ABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBFA=
Address = 10.0.0.2/32
ListenPort = 51822

# port forwarding
PreUp = iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination 192.0.2.3
PreUp = iptables -t nat -A POSTROUTING -p tcp --dport 80 -j MASQUERADE

# remote settings for Endpoint A
[Peer]
PublicKey = /TOE4TKtAqVsePRVR+5AA43HkAK5DSntkOCO7nYq5xU=
AllowedIPs = 10.0.0.1/32

The first iptables rule above will forward packets that the container receives through the WireGuard tunnel at TCP port 80 on to the webserver’s IP address of 192.0.2.3. The second iptables rule will masquerade those packets as if they had originated from the container itself, so that (in tandem with the masquerading that the Podman host provides for the container) the external webserver will send responses to them back through Endpoint B (where the Podman host will send those responses back to the container, for the container to forward back to Endpoint A).

Note

Unlike the previous Use for Site Port Forwarding and Use for Inbound Port Forwarding examples, we don’t need to adjust the privileged port restrictions on the host, or adjust the host’s firewall for the forwarded port, since this scenario doesn’t manipulate packets sent to port 80 on the host itself — only port 80 packets that have been tunneled into the container.

Run a container for this WireGuard configuration with the following podman run command:

podman run \
    --cap-add NET_ADMIN \
    --cap-add NET_RAW \
    --publish 51822:51822/udp \
    --name wg-ofw \
    --rm \
    --sysctl net.ipv4.conf.all.forwarding=1 \
    --volume /srv/wg-ofw/conf:/etc/wireguard:Z \
    docker.io/procustodibus/wireguard

Alternately, you can place the following docker-compose.yml file in the directory above the WireGuard configuration file:

# /srv/wg-ofw/docker-compose.yml
version: '3'
services:
  wireguard:
    image: docker.io/procustodibus/wireguard
    cap_add:
    - NET_ADMIN
    - NET_RAW
    ports:
    - 51822:51822/udp
    sysctls:
    - net.ipv4.conf.all.forwarding=1
    volumes:
    - ./conf:/etc/wireguard:Z

And then start up the container by running podman-compose up from the same directory as the docker-compose.yml file.

The Podman configuration for this example is very similar to the rootfull Docker Use for Outbound Port Forwarding example from the original WireGuard Containers article, with the following differences for Podman:

  1. We granted the NET_RAW capability (to run iptables in a rootless container).

  2. We explicitly turned on the packet forwarding kernel parameter.

  3. We added the Z option to the WireGuard config volume.

  4. We qualified the image name with the docker.io registry.

Use for Host Network

WireGuard Podman Host Network

You can use a Podman container to provide access for the Podman host itself to the container’s WireGuard network, if you want to use the host itself as a regular point in a point-to-point topology or point-to-site topology, or as a generic spoke in a hub-and-spoke topology. However, in this scenario you must run the container as root, in order to set up the WireGuard interface inside the host’s root network namespace.

Important

In order to run this example, make sure you load the WireGuard kernel module on the Podman host as described by the Kernel Module Loading section at the beginning of this article.

For this example, we’ll use the exact same WireGuard configuration as “Endpoint A” from the WireGuard Point to Point Configuration guide. Save the WireGuard configuration for the container in its own directory somewhere convenient on the host, like in the /srv/wg-host/conf/ directory:

# /srv/wg-host/conf/wg0.conf

# local settings for Endpoint A
[Interface]
PrivateKey = AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEE=
Address = 10.0.0.1/32
ListenPort = 51821

# remote settings for Endpoint B
[Peer]
PublicKey = fE/wdxzl0klVp/IR8UcaoGUMjqaWi3jAd7KzHKFS6Ds=
Endpoint = 203.0.113.2:51822
AllowedIPs = 10.0.0.2/32

Then you can run a container for this WireGuard configuration with the following podman run command:

sudo podman run \
    --cap-add NET_ADMIN \
    --name wg-host \
    --network host \
    --rm \
    --volume /srv/wg-host/conf:/etc/wireguard:Z \
    docker.io/procustodibus/wireguard

When you run the container this way, with the --network host flag, it will expose the WireGuard network to the rest of the processes on the Podman host. So if, for example, you have an HTTP server running on Endpoint B (10.0.0.2) in the WireGuard network (like we do in the scenario for the WireGuard Point to Point Configuration guide), you’ll be able to access that webserver from the Podman host using cURL (or any web browser) like the following:

$ curl 10.0.0.2:80
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
...

Alternately, you can run the WireGuard container with the podman-compose command if you place the following docker-compose.yml file in the directory above the WireGuard configuration file:

# /srv/wg-host/docker-compose.yml
version: '3'
services:
  wireguard:
    image: docker.io/procustodibus/wireguard
    cap_add:
    - NET_ADMIN
    network_mode: host
    volumes:
    - ./conf:/etc/wireguard:Z

And then start up the container by running sudo podman-compose up from the same directory as the docker-compose.yml file.

Note

This example requires Podman Compose version 1.0.4 or newer (see the Network Mode With Podman Compose section).

The Podman configuration for this example is very similar to the Docker Use for Host Network example from the original WireGuard Containers article, with the following differences for Podman:

  1. We added the Z option to the WireGuard config volume.

  2. We qualified the image name with the docker.io registry.

Use for Host Network With Default Route

WireGuard Podman Host Network With Default Route

You can also use a Podman container to provide access to the Internet for the host itself through the container’s WireGuard network. However, in this scenario you must also run the container as root, in order for it to set up the WireGuard interface inside the host’s root network namespace.

Important

In order to run this example, make sure you load the WireGuard and iptables kernel modules on the Podman host as described by the Kernel Module Loading section at the beginning of this article.

For this example, we’ll use a WireGuard configuration similar to “Endpoint A” from the WireGuard Point to Site Configuration guide. Save the WireGuard configuration for the container in its own directory somewhere convenient on the host, like in the /srv/wg-host-internet/conf/ directory:

# /srv/wg-host-internet/conf/wg0.conf

# local settings for Endpoint A
[Interface]
PrivateKey = AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEE=
Address = 10.0.0.1/32
ListenPort = 51821

# remote settings for Host β
[Peer]
PublicKey = fE/wdxzl0klVp/IR8UcaoGUMjqaWi3jAd7KzHKFS6Ds=
Endpoint = 203.0.113.2:51822
AllowedIPs = 0.0.0.0/0

The one difference between this example and the scenario from the WireGuard Point to Site Configuration guide, however, is that in this example the Internet is the “site” to which Host β will provide access, instead of just Host β’s own local network. So we’ve changed the AllowedIPs setting in Endpoint A’s WireGuard config to be 0.0.0.0/0 (the entire IPv4 address space).

This AllowedIPs setting will trigger the WireGuard script used in the Podman container to take over host’s default route, sending all of the host’s non-local traffic through the WireGuard tunnel. This also requires you to turn on the ipv4.conf.all.src_valid_mark kernel parameter outside of the container:

$ sudo sysctl -w net.ipv4.conf.all.src_valid_mark=1
Note

This only temporarily turns on this kernel parameter — to permanently change it, add it to some file in your /etc/sysctl.d/ directory (or to your /etc/sysctl.conf file).

With that kernel parameter set, you can run the WireGuard container as root with the following podman run command:

sudo podman run \
    --cap-add NET_ADMIN \
    --cap-add NET_RAW \
    --name wg-host-internet \
    --network host \
    --rm \
    --volume /srv/wg-host-internet/conf:/etc/wireguard:Z \
    docker.io/procustodibus/wireguard

The host will now send all of its Internet traffic through the WireGuard tunnel. If you run the following command on Endpoint A, it will demonstrate that it’s now using the IP address from Host β to access the Internet:

$ curl icanhazip.com
203.0.113.2

Alternately, you can run the WireGuard container via the podman-compose command if you place the following docker-compose.yml file in the directory above the WireGuard configuration file:

# /srv/wg-host-internet/docker-compose.yml
version: '3'
services:
  wireguard:
    image: docker.io/procustodibus/wireguard
    cap_add:
    - NET_ADMIN
    - NET_RAW
    network_mode: host
    volumes:
    - ./conf:/etc/wireguard:Z

And then start up the container by running sudo podman-compose up from the same directory as the docker-compose.yml file.

Note

This example requires Podman Compose version 1.0.4 or newer (see the Network Mode With Podman Compose section).

Use for Container Network

WireGuard Podman Container Network

You can also use a Podman container to provide access to a WireGuard network for other selected containers on the Podman host. The simplest way to do this is to run the other containers in the first container’s network namespace.

Important

In order to run this example, make sure you load the WireGuard kernel module on the Podman host, and open up access to the host’s UDP port 51822, as described by the Kernel Module Loading and Firewall Modifications sections at the beginning of this article.

For this example, we’ll use the exact same WireGuard configuration as “Endpoint B” from the WireGuard Point to Point Configuration guide. Save the WireGuard configuration for the container in its own directory somewhere convenient on the host, like in the /srv/wg-point/conf/ directory:

# /srv/wg-point/conf/wg0.conf

# local settings for Endpoint B
[Interface]
PrivateKey = ABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBFA=
Address = 10.0.0.2/32
ListenPort = 51822

# remote settings for Endpoint A
[Peer]
PublicKey = /TOE4TKtAqVsePRVR+5AA43HkAK5DSntkOCO7nYq5xU=
AllowedIPs = 10.0.0.1/32

Run a container for this WireGuard configuration with the following podman run command:

podman run \
    --cap-add NET_ADMIN \
    --publish 51822:51822/udp \
    --name wg-point \
    --rm \
    --volume /srv/wg-point/conf:/etc/wireguard:Z \
    docker.io/procustodibus/wireguard

And with this container running (to which we’ve given the arbitrary name “wg-point”), use the --network container:wg-point flag to run each sibling container with which you want to share the WireGuard network, like this:

podman run \
    --name example-web-server \
    --network container:wg-point \
    --rm \
    docker.io/nginx

The above example-web-server container will start up a generic nginx webserver, listening on TCP port 80 inside the WireGuard container’s own network namespace. This allows you to access the webserver from within the WireGuard network using the WireGuard container’s own WireGuard IP address (10.0.0.2). For example, we can access it like this with cURL from Endpoint A:

$ curl 10.0.0.2:80
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...

Alternately, you can run the WireGuard container together with its siblings using the podman-compose command if you place the following docker-compose.yml file in the directory above the WireGuard configuration file:

# /srv/wg-point/docker-compose.yml
version: '3'
services:
  wg-point:
    image: docker.io/procustodibus/wireguard
    cap_add:
    - NET_ADMIN
    ports:
    - 51822:51822/udp
    volumes:
    - ./conf:/etc/wireguard:Z
  example-web-server:
    image: docker.io/nginx
    network_mode: 'service:wireguard'

Then start up the containers by running podman-compose up from the same directory as the docker-compose.yml file.

Note

This example requires Podman Compose version 1.0.4 or newer (see the Network Mode With Podman Compose section).

The Podman configuration for this example is similar to the Docker Use for Container Network example from the original WireGuard Containers article — but we’ve reversed the roles between Endpoint A and Endpoint B to match the Use for Bridge Network and Use for Pod Network examples later in this article. If we had kept the roles the same, the only differences between this Podman example and the Docker example from the other article would be that, for Podman:

  1. We added the Z option to the WireGuard config volume.

  2. We qualified the image name with the docker.io registry.

Use for Bridge Network

WireGuard Remote Access Through Container

Another way for a Podman container to provide WireGuard access to other containers is to attach it and the other containers to the same bridge network. This essentially creates a WireGuard point-to-site topology, where the “site” is the container’s bridge network, instead of the host’s LAN.

We’ll in fact use the same WireGuard configuration for the Podman container in this example as “Host β” uses from the WireGuard Point to Site Configuration guide — with two exceptions:

  1. We won’t set the “IP forwarding” kernel parameter in the WireGuard configuration (we’ll set it instead in the Podman configuration).

  2. We can simplify the container’s iptables rules to a single line (to masquerade all forwarded packets except those sent out its WireGuard interface).

Important

In order to run this example, make sure you load the WireGuard and iptables kernel modules on the Podman host, and open up access to the host’s UDP port 51822, as described by the Kernel Module Loading and Firewall Modifications sections at the beginning of this article.

Save the WireGuard configuration for Endpoint B in its own directory somewhere convenient on the host, like in the /srv/wg-net-masq/wg-server/ directory:

# /srv/wg-net-masq/wg-server/wg0.conf

# local settings for Endpoint B
[Interface]
PrivateKey = ABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBFA=
Address = 10.0.0.2/32
ListenPort = 51822

# IP masquerading
PreUp = iptables -t nat -A POSTROUTING ! -o %i -j MASQUERADE

# remote settings for Endpoint A
[Peer]
PublicKey = /TOE4TKtAqVsePRVR+5AA43HkAK5DSntkOCO7nYq5xU=
AllowedIPs = 10.0.0.1/32

Next, create the bridge network for the containers to use by running the following command:

$ podman network create \
    --subnet 192.168.123.0/24 \
    wg-network

You can use a different subnet than 192.168.123.0/24 if you like, and a different name than wg-network — just make sure you adjust the configuration for the containers accordingly.

Finally, start up the WireGuard container, as well as the other containers you want to expose through WireGuard. For each container, specify the network name we just created, and an available IP address in the network:

$ podman run \
    --cap-add NET_ADMIN \
    --cap-add NET_RAW \
    --name wg-server \
    --network wg-network \
    --ip 192.168.123.123 \
    --publish 51822:51822/udp \
    --rm \
    --sysctl net.ipv4.conf.all.forwarding=1 \
    --volume /srv/wg-net-masq/conf:/etc/wireguard:Z \
    docker.io/procustodibus/wireguard
$ podman run \
    --name example-web-server \
    --network wg-network \
    --ip 192.168.123.2 \
    --rm \
    docker.io/nginx

The order in which you start up containers doesn’t matter. If you had previously started up a container that you now want to expose via WireGuard, you can connect it to the bridge network with the following command:

$ podman network connect \
    --ip 192.168.123.2 \
    wg-network \
    example-web-server

On Endpoint A (the remote host), make sure you include the bridge network’s subnet in the AllowedIPs setting of Endpoint A’s WireGuard configuration:

# /etc/wireguard/wg0.conf

# local settings for Endpoint A
[Interface]
PrivateKey = AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEE=
Address = 10.0.0.1/32
ListenPort = 51821

# remote settings for Endpoint B
[Peer]
PublicKey = fE/wdxzl0klVp/IR8UcaoGUMjqaWi3jAd7KzHKFS6Ds=
Endpoint = 203.0.113.2:51822
AllowedIPs = 192.168.123.0/24

Then you’ll be able to access the containers on Endpoint B from Endpoint A by using their IP address on the bridge network. For example, we can access the example-web-server container from Endpoint A by using its bridge network address of 192.168.123.2:

$ curl 192.168.123.2:80
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...

Alternatively, you can use Podman Compose to set up the bridge network and containers. For example, using the Docker Compose version 3.5+ syntax, you can create a similar wg-network to the above, and connect similar wg-server and example-web-server containers to it:

# /srv/wg-net-masq/docker-compose.yml
version: '3.5'

networks:
  wg-network:
    ipam:
      config:
      - subnet: 192.168.123.0/24

services:
  wg-server:
    image: docker.io/procustodibus/wireguard
    cap_add:
    - NET_ADMIN
    - NET_RAW
    networks:
      wg-network:
        ipv4_address: 192.168.123.123
    ports:
    - 51822:51822/udp
    sysctls:
    - net.ipv4.conf.all.forwarding=1
    volumes:
    - ./conf:/etc/wireguard:Z

  example-web-server:
    image: docker.io/nginx
    networks:
      wg-network:
        ipv4_address: 192.168.123.2

Start up the network and containers by running podman-compose up from the same directory as the docker-compose.yml file.

Note

This example requires Podman Compose version 1.0.4 or newer (see the Network Mode With Podman Compose section).

The Podman configuration for this example is very similar to the rootfull Docker WireGuard in a Container example from the WireGuard Remote Access to Docker Containers article, with the following differences for the Podman WireGuard container:

  1. We granted the NET_RAW capability (to run iptables in a rootless container).

  2. We explicitly turned on the packet forwarding kernel parameter.

  3. We added the Z option to the WireGuard config volume.

  4. We qualified the image name with the docker.io registry.

Use for Bridge Network Without Masquerading

WireGuard Remote Access Without Masquerading

One downside of the Use for Bridge Network scenario above is we had to add a masquerading iptables rule to the WireGuard container. This means traffic forwarded to the other containers on the bridge network from the WireGuard network will appear to have the WireGuard container’s own IP address as its source (192.168.123.123), instead of the traffic’s original source addresses.

To maintain the traffic’s original source addresses, we’d have to manually add a route (or routes) to the network namespace of each container to which the WireGuard container forwards traffic. In the above scenario, we assigned the WireGuard container an IP address of 192.168.123.123, and the container’s WireGuard configuration is set to allow only packets from 10.0.0.1/32 (Endpoint A); so, for that scenario, we’d have to add the following route to the example-web-server container:

ip route add 10.0.0.1/32 via 192.168.123.123

If the WireGuard container’s WireGuard config contained multiple AllowedIPs address blocks, we’d have to add a route for each block. For example, if the config contained the following:

AllowedIPs = 10.0.0.1/32
AllowedIPs = 10.1.2.3/32, 10.1.2.99/32
AllowedIPs = 192.168.234.0/24

We’d have to add four routes, one for each address block:

ip route add 10.0.0.1/32 via 192.168.123.123
ip route add 10.1.2.3/32 via 192.168.123.123
ip route add 10.1.2.99/32 via 192.168.123.123
ip route add 192.168.234.0/24 via 192.168.123.123

In order to add routes to the container, you have to run the ip route add command from either a) the host, using the container’s namespace, or b) within the container itself. To add routes from the host, use the nsenter command as described by the Add the Route From the Host section of the WireGuard Remote Access to Docker Containers article.

To add routes from within a rootless Podman container, you have to:

  1. Install the iproute2 package in the container (or, ideally, the container’s image).

  2. Start up the container with the NET_ADMIN capability.

  3. Run the ip route add command from within the container.

How you do this exactly depends on the container’s image, but usually this means you have to:

  1. Create a custom image with a custom Dockerfile in which you install the iproute2 package.

  2. Run the image with a custom script command (and with the --cap-add NET_ADMIN flag).

  3. As the first part of the script, run the ip route add command.

  4. As the second part of the script, run the default command from the original base image.

For our example web server, we would create a custom Dockerfile like the following (placed in its own directory somewhere convenient on the host, like the /srv/wg-net-route/example-web-server/ directory), which overrides the base nginx image to install the iproute2 package:

# /srv/wg-net-route/example-web-server/Dockerfile
FROM docker.io/nginx
RUN apt-get update && apt-get install -y iproute2

Then we would create a custom command script (placed at /srv/wg-net-route/example-web-server/command.sh) that first runs the ip route add command, and then runs the command to start nginx:

#!/bin/sh -e
# /srv/wg-net-route/example-web-server/command.sh
ip route add 10.0.0.1/32 via 192.168.123.123
nginx -g 'daemon off;'

And we’d build the custom image like so, assigning it a tag of custom-nginx:

podman build \
    --tag custom-nginx \
    /srv/wg-net-route/example-web-server

Next, we’d save the WireGuard configuration for Endpoint B in a different directory on the host (like /srv/wg-net-route/wg-server/):

# /srv/wg-net-route/wg-server/wg0.conf

# local settings for Endpoint B
[Interface]
PrivateKey = ABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBFA=
Address = 10.0.0.2/32
ListenPort = 51822

# remote settings for Endpoint A
[Peer]
PublicKey = /TOE4TKtAqVsePRVR+5AA43HkAK5DSntkOCO7nYq5xU=
AllowedIPs = 10.0.0.1/32

The WireGuard configuration should be the same as for the Use for Container Network example above, just without the masquerading iptables rule.

Important

In order to run this example, make sure you load the WireGuard kernel module on the Podman host, and open up access to the host’s UDP port 51822, as described by the Kernel Module Loading and Firewall Modifications sections at the beginning of this article.

Then we’d create the bridge network for the containers to use by running the following command:

$ podman network create \
    --subnet 192.168.123.0/24 \
    wg-network

And start up the WireGuard container, attaching it to the bridge network:

$ podman run \
    --cap-add NET_ADMIN \
    --name wg-server \
    --network wg-network \
    --ip 192.168.123.123 \
    --publish 51822:51822/udp \
    --rm \
    --sysctl net.ipv4.conf.all.forwarding=1 \
    --volume /srv/wg-net-route/wg-server:/etc/wireguard:Z \
    docker.io/procustodibus/wireguard

Unlike the Use for Container Network scenario, we don’t need to add the NET_RAW capability to the WireGuard container, since we don’t need to run any iptables rules inside it.

Finally, we can run a container with the custom nginx image we built above, attaching it to the same bridge network:

$ podman run \
    --cap-add NET_ADMIN \
    --name example-web-server \
    --network wg-network \
    --ip 192.168.123.2 \
    --rm \
    --volume /srv/wg-net-route/example-web-server:/custom:Z \
    custom-nginx \
    /custom/command.sh

Because this container will run the ip route add command, it needs to be granted the NET_ADMIN capability. And because we do so via the custom command.sh script we wrote above, we also need to mount that script to the /custom/ directory in the container (or to some other convenient mount point within the container).

With that route added, we can test out the WireGuard tunnel by trying to access the custom container by its address on the bridge network (192.168.123.2) from Endpoint A:

$ curl 192.168.123.2:80
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...

Alternatively, you can use Podman Compose to build the custom image, and run it in a bridge network with the WireGuard container. For example, using the Docker Compose version 3.5+ syntax, you can create a similar wg-network to the above, and connect similar wg-server and example-web-server containers to it:

# /srv/wg-net-route/docker-compose.yml
version: '3.5'

networks:
  wg-network:
    ipam:
      config:
      - subnet: 192.168.123.0/24

services:
  wg-server:
    image: docker.io/procustodibus/wireguard
    cap_add:
    - NET_ADMIN
    networks:
      wg-network:
        ipv4_address: 192.168.123.123
    ports:
    - 51822:51822/udp
    sysctls:
    - net.ipv4.conf.all.forwarding=1
    volumes:
    - ./wg-server:/etc/wireguard:Z

  example-web-server:
    build: example-web-server
    cap_add:
    - NET_ADMIN
    command: /custom/command.sh
    networks:
      wg-network:
        ipv4_address: 192.168.123.2
    volumes:
    - ./example-web-server:/custom:Z

Start up the network and containers by running podman-compose up from the same directory as the docker-compose.yml file.

Note

This example requires Podman Compose version 1.0.4 or newer (see the Network Mode With Podman Compose section).

The Podman configuration for this example is very similar to the rootfull Docker WireGuard in a Container Without Masquerading example from the WireGuard Remote Access to Docker Containers article, with the following differences for the Podman WireGuard container:

  1. We explicitly turned on the packet forwarding kernel parameter.

  2. We added the Z option to the WireGuard config volume.

  3. We qualified the image name with the docker.io registry.

Use for Bridge Network With WireGuard on Host

WireGuard Remote Access Through Host

If you run WireGuard on the Podman host itself, instead of within a container, you can also allow remote endpoints of the WireGuard network to access Podman containers on a bridge network — as long as you run those containers (and the bridge network itself) as root.

Important

In order to run this example, make sure you open up access to the host’s UDP port 51822, as described by the Firewall Modifications section at the beginning of this article.

Save the WireGuard configuration for the host in the /etc/wireguard/ directory:

# /etc/wireguard/wg0.conf

# local settings for Endpoint B
[Interface]
PrivateKey = ABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBFA=
Address = 10.0.0.2/32
ListenPort = 51822

# remote settings for Endpoint A
[Peer]
PublicKey = /TOE4TKtAqVsePRVR+5AA43HkAK5DSntkOCO7nYq5xU=
AllowedIPs = 10.0.0.1/32

Because WireGuard will be running in the host’s root namespace, we don’t need any masquerading iptables rules in this scenario. Start up the WireGuard interface by running the following command on the host:

$ sudo wg-quick up wg0

Next, create the bridge network for the containers to use by running the following command as root:

$ sudo podman network create \
    --subnet 192.168.123.0/24 \
    wg-network

Finally, again as root, start up the containers you want to expose through WireGuard. For each container, specify the network name we just created, and an available IP address in the network:

$ sudo podman run \
    --name example-web-server \
    --network wg-network \
    --ip 192.168.123.2 \
    --rm \
    docker.io/nginx

If you don’t have a firewall set up on the host, you’ll immediately be able to access the example-web-server container from Endpoint A. If you do have a firewall on the host, however, you’ll need to adjust the firewall to allow the host’s WireGuard interface (wg0) access to the wg-network Podman network (192.168.123.0/24).

Note

This is in addition to allowing access through your firewall to the WireGuard listen port (51822 in this example), as described in the Firewall Modifications section.

If you’re using firewalld to manage your firewall, the easiest way to do this is simply to add the WireGuard interface to firewalld’s trusted zone:

$ sudo firewall-cmd --zone=trusted --add-interface=wg0
success

Otherwise, if you’re using nftables or iptables directly, you’ll probably need to add a rule like iifname wg0 ip daddr 192.168.123.0/24 accept to the forward chain of your filter table (for nftables), or something like -A FORWARD -i wg0 -d 192.168.123.0/24 -j ACCEPT for iptables.

With the firewall adjusted, you’ll now be able to access the example-web-server container from Endpoint A by using its bridge network address of 192.168.123.2:

$ curl 192.168.123.2:80
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...

Alternatively, you can use Podman Compose to set up the bridge network and containers. For example, using the Docker Compose version 3.5+ syntax, you can create a similar wg-network to the above, and connect a similar example-web-server container to it:

# /srv/wg-net-host/docker-compose.yml
version: '3.5'

networks:
  wg-network:
    ipam:
      config:
      - subnet: 192.168.123.0/24

  example-web-server:
    image: docker.io/nginx
    networks:
      wg-network:
        ipv4_address: 192.168.123.2

Start up the network and containers by running sudo podman-compose up from the same directory as the docker-compose.yml file.

Note

This example requires Podman Compose version 1.0.4 or newer (see the Network Mode With Podman Compose section).

The Podman configuration for this example is very similar to the Docker WireGuard on the Host example from the WireGuard Remote Access to Docker Containers article. The only real difference is that Docker adds a few more restrictive firewall rules than Podman (when not using firewalld).

Use for Pod Network

WireGuard Podman Pod Network

With Podman, the most idiomatic way to use a Podman container to provide access for other selected containers to a WireGuard network is to run it and the containers together in the same pod. This allows all the containers to share the same network namespace.

Important

In order to run this example, make sure you load the WireGuard kernel module on the Podman host, and open up access to the host’s UDP port 51822, as described by the Kernel Module Loading and Firewall Modifications sections at the beginning of this article.

For this example, we’ll use the exact same WireGuard configuration as “Endpoint B” from the WireGuard Point to Point Configuration guide. Save the WireGuard configuration for the container in its own directory somewhere convenient on the host, like in the /srv/wg-pod/conf/ directory:

# /srv/wg-pod/conf/wg0.conf

# local settings for Endpoint B
[Interface]
PrivateKey = ABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBFA=
Address = 10.0.0.2/32
ListenPort = 51822

# remote settings for Endpoint A
[Peer]
PublicKey = /TOE4TKtAqVsePRVR+5AA43HkAK5DSntkOCO7nYq5xU=
AllowedIPs = 10.0.0.1/32

Then create a new pod with the following podman pod create command, publishing the WireGuard container’s listen port (51822), and giving the pod an arbitrary name like wg-pod:

podman pod create \
    --publish 51822:51822/udp \
    --name wg-pod

Next, create the WireGuard container for the pod with following podman create command:

podman create \
    --cap-add NET_ADMIN \
    --name wg-server \
    --pod wg-pod \
    --rm \
    --volume /srv/wg-pod/conf:/etc/wireguard:Z \
    docker.io/procustodibus/wireguard

Notice that with this command, we’ve used the --pod flag to specify the pod in which to run the container, and haven’t included a --publish flag for the WireGuard listen port (since it must be published for the pod itself, not the container).

Then add to the pod the other containers with which you want to share the WireGuard network:

podman create \
    --name example-web-server \
    --pod wg-pod \
    --rm \
    docker.io/nginx

Finally, start the pod:

podman pod start wg-pod

The above example-web-server container will start up a generic nginx webserver, listening on TCP port 80 inside the pod’s network namespace. This allows you to access the webserver from within the WireGuard network using the WireGuard IP address of the WireGuard container (10.0.0.2). For example, we can access it like this with cURL from Endpoint A:

$ curl 10.0.0.2:80
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...

Note that there is no equivalent way to run containers in a pod using Docker, Docker Compose, or Podman Compose. However, this technique is somewhat similar to running multiple containers inside the same container network, as described in the Use for Container Network example above.

Use for Port Forwarding From Internet

WireGuard Podman Forwarding From Internet

Our last example will show you how to use a Podman container as the target of port forwarding through WireGuard from the Internet — and without masquerading. We’ll in effect be replicating the private server from the WireGuard Port Forwarding From the Internet article, but running the private server’s WireGuard server and webserver as two Podman containers in the same pod.

In this example, we’ll call the public server “Endpoint A”, and the private server “Endpoint B”, for consistency with the Use for Inbound Port Forwarding section of this article. That section shows how to run WireGuard in a Podman container on the public server of a similar scenario (albeit one that uses masquerading); this section shows how to run WireGuard in a Podman container on the private server (and in a scenario where the public server doesn’t provide masquerading).

Important

In order to run this example, make sure you load the WireGuard and iptables kernel modules on the Podman host, and open up access to the host’s UDP port 51822, as described by the Kernel Module Loading and Firewall Modifications sections at the beginning of this article.

So that we don’t need to use masquerading on the public server (Endpoint A), we’ll use the following WireGuard configuration on the private server (Endpoint B), with its AllowedIPs setting configured for the entire IPv4 address range (0.0.0.0/0). Save the WireGuard configuration for the container in its own directory somewhere convenient on the host, like in the /srv/wg-internet/conf/ directory:

# /srv/wg-internet/conf/wg0.conf

# local settings for Endpoint B
[Interface]
PrivateKey = ABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBFA=
Address = 10.0.0.2/32
ListenPort = 51822

# remote settings for Endpoint A
[Peer]
PublicKey = /TOE4TKtAqVsePRVR+5AA43HkAK5DSntkOCO7nYq5xU=
Endpoint = 198.51.100.1:51821
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25

This is the same technique shown by the Default Route section of the WireGuard Port Forwarding From the Internet article; but by running WireGuard in a pod, WireGuard will take over the default route just for the pod, instead of for the entire host.

Next, create the pod with the following podman pod create command, publishing the WireGuard container’s listen port (51822), and giving the pod an arbitrary name like wg-pod:

podman pod create \
    --publish 51822:51822/udp \
    --name wg-pod

Then create the WireGuard container for the pod with following podman create command:

podman create \
    --cap-add NET_ADMIN \
    --cap-add NET_RAW \
    --name wg-server \
    --pod wg-pod \
    --rm \
    --sysctl net.ipv4.conf.all.src_valid_mark=1 \
    --volume /srv/wg-internet/conf:/etc/wireguard:Z \
    docker.io/procustodibus/wireguard

Notice that for this command, compared to the Use for Pod Network example above, we’ve added the NET_RAW capability, and turned on the ipv4.conf.all.src_valid_mark kernel parameter. These are needed to allow the extra work that the WireGuard script does when setting up the default route (triggered by the AllowedIPs = 0.0.0.0/0 setting in our WireGuard config above).

Next, add the other container (or containers) that will serve as the target of the port forwarding. In this case, we’ll run a generic webserver:

podman create \
    --name example-web-server \
    --pod wg-pod \
    --rm \
    docker.io/nginx

Finally, start the pod:

podman pod start wg-pod

With the public server, Endpoint A, configured to forward TCP port 80 on to Endpoint B as shown by the WireGuard Port Forwarding From the Internet article (or set up using Podman as shown by the Use for Inbound Port Forwarding section above, but without the masquerading iptables rule), you can access the generic webserver running in the pod by using the public IP address of the public server:

$ curl 198.51.100.1:80
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...

Troubleshooting

Not Supported Error

If you encounter a not supported error when the WireGuard container starts up, like the following:

[#] ip link add wg0 type wireguard
Error: Unknown device type.
Unable to access interface: Protocol not supported

It means either you don’t have the WireGuard kernel module installed on the host, or the module has not yet been loaded. Make sure you follow the Kernel Module Loading instructions at the beginning of this article.

Can’t Initialize iptables

If you encounter a can’t initialize iptables error when the WireGuard container starts up, like this:

iptables v1.8.8 (legacy): can't initialize iptables table `nat': Permission denied (you must be root)
Perhaps iptables or your kernel needs to be upgraded.

Or like this:

iptables v1.8.8 (legacy): can't initialize iptables table `nat': Table does not exist (do you need to insmod?)

It means either you haven’t loaded the necessary iptables kernel modules, or you haven’t granted the container the NET_RAW capability. Make sure you follow the Kernel Module Loading instructions at the beginning of the article (and you grant the container the NET_RAW capability).

Update 2023-12-27

With the latest container images, built on Alpine Linux 3.19 (which uses the nftables kernel backend), you may instead encounter error messages like the following:

iptables: Failed to initialize nft: Protocol not supported

Or like this:

Warning: Extension MASQUERADE revision 0 not supported, missing kernel module?
iptables v1.8.10 (nf_tables):  RULE_APPEND failed (No such file or directory): rule in chain POSTROUTING

Or like this:

Warning: Extension DNAT revision 0 not supported, missing kernel module?
iptables v1.8.10 (nf_tables):  CHAIN_ADD failed (No such file or directory): chain PREROUTING

Or like this:

mnl.c:61: Unable to initialize Netlink socket: Protocol not supported

Or like this:

[#] nft -f /dev/fd/63
/dev/fd/63:5:1-96: Error: Could not process rule: No such file or directory

If so, you need to load the nftables kernel modules as directed by the Kernel Module Loading instructions at the beginning of the article.

Error From slirp4netns

If you encounter an error from slirpnets when the WireGuard container starts up, like the following:

Error: error from slirp4netns while setting up port redirection: map[desc:bad request: add_hostfwd: slirp_add_hostfwd failed]

It means you’re trying to publish a privileged port for a rootless container. Either lower the net.ipv4.ip_unprivileged_port_start kernel parameter to (or below) the port you’re trying to publish, or follow one of the other suggestions from the Allow non-root process to bind to port 80 and 443? Super User question.

Error Setting src_valid_mark

If you see an error regarding the net.ipv4.conf.all.src_valid_mark kernel parameter when the WireGuard container starts up, like the following:

sysctl: error setting key 'net.ipv4.conf.all.src_valid_mark': Read-only file system

It means that you’ve configured WireGuard to take over the default route (eg via AllowedIPs = 0.0.0.0/0 or AllowedIPs = ::/0), but are missing some necessary pre-conditions of the wg-quick script that allow it to do so. Make sure you:

  1. Turn on the net.ipv4.conf.all.src_valid_mark kernel parameter.

  2. Grant the WireGuard container the NET_RAW capability.

  3. Load all of the following kernel modules:

# WireGuard module
wireguard
# iptables modules for wg-quick default route
iptable_mangle
iptable_raw
xt_addrtype
xt_comment
xt_connmark
xt_mark

Update 2023-12-27

With the latest container images, built on Alpine Linux 3.19 (which uses the nftables kernel backend), you will need to load several nftables kernel modules in place of iptables modules. Load these kernel modules to use WireGuard for the default route:

# WireGuard module
wireguard
# nftables modules for wg-quick default route
nft_ct
nft_fib_inet

Follow the Use for Host Network With Default Route example when running a rootfull WireGuard container, and the Use for Port Forwarding From Internet example when running a rootless WireGuard container.

Diagnostic Tools

To diagnose other issues, you can shell into a Podman container using the podman exec command, like the following for a container named wg-point:

$ podman exec -it wg-point sh
/ #

For Podman containers that have been started via the podman-compose up command, you can shell into them using the podman-compose exec command from the same directory as their docker-compose.yml definition; like the following for a service named wg-server:

$ podman-compose exec wg-server sh
/ #

You can also use the nsenter utility to run diagnostic tools installed on the host from within a Podman container’s network namespace. First use the podman container inspect command to identify the PID (Process ID) of the container, like the following for a container named wg-point:

$ podman container inspect wg-point -f '{{.State.Pid}}`
12345

Then use the nsenter -t [PID] -n [PROGRAM COMMAND] command to run any program installed on the host, like tcpdump:

$ sudo nsenter -t 12345 -n tcpdump -niany tcp port 80
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on any, link-type LINUX_SLL (Linux cooked v1), capture size 262144 bytes
03:49:52.374483 IP 198.51.100.1.48764 > 10.0.2.100.80: Flags [S], seq 836029496, win 62727, options [mss 8961,sackOK,TS val 427845568 ecr 0,nop,wscale 6], length 0
03:49:52.374508 IP 198.51.100.1.48764 > 10.0.0.1.80: Flags [S], seq 836029496, win 62727, options [mss 8961,sackOK,TS val 427845568 ecr 0,nop,wscale 6], length 0
...

See the Shell Into the WireGuard Container and Run Diagnostic Tools in the WireGuard Namespace sections from the original WireGuard Containers article for further examples.