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):
-
Use for Host Network (rootfull)
-
Use for Host Network With Default Route (rootfull)
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 |
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
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
In the above example, the (IPv4) module’s name is |
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 |
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
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 |
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:
-
--cap-add NET_ADMIN
: Grants the container theNET_ADMIN
capability — this is required to start up a WireGuard interface inside the container. -
--name wg-hub
: Sets the container’s name towg-hub
(you can set this to whatever name you want, or omit it entirely if you don’t care how it’s named). -
--publish 51823:51823/udp
: Forwards the host’s public51823
UDP port to the container’s51823
UDP port — make sure the latter matches theListenPort
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. -
--rm
: Deletes the container when it’s shut down (you can omit this if you don’t want to delete the container). -
--sysctl net.ipv4.conf.all.forwarding=1
: Enables packet forwarding in the container. -
--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). TheZ
option allows the container to access this directory even when SELinux is enabled. -
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:
-
We explicitly turned on the packet forwarding kernel parameter.
-
We added the
Z
option to the WireGuard config volume. -
We qualified the image name with the
docker.io
registry.
Use for Site Masquerading
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:
-
We won’t set the “IP forwarding” kernel parameter in the WireGuard configuration (we’ll set it instead in the Podman configuration).
-
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 |
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:
-
We granted the
NET_RAW
capability (to runiptables
in a rootless container). -
We explicitly turned on the packet forwarding kernel parameter.
-
We added the
Z
option to the WireGuard config volume. -
We qualified the image name with the
docker.io
registry.
Use for Site Port Forwarding
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 |
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 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 ( |
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:
-
We granted the
NET_RAW
capability (to runiptables
in a rootless container). -
We set
slirp4netns
as the port-forwarding handler (to allow port-forwarding from the host to a destination outside of the container). -
We explicitly turned on the packet forwarding kernel parameter.
-
We added the
Z
option to the WireGuard config volume. -
We qualified the image name with the
docker.io
registry.
Use for Inbound Port Forwarding
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 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 ( |
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:
-
We granted the
NET_RAW
capability (to runiptables
in a rootless container). -
We set
slirp4netns
as the port-forwarding handler (to allow port-forwarding from the host to a destination outside of the container). -
We explicitly turned on the packet forwarding kernel parameter.
-
We added the
Z
option to the WireGuard config volume. -
We qualified the image name with the
docker.io
registry.
Use for Outbound Port Forwarding
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 |
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 |
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:
-
We granted the
NET_RAW
capability (to runiptables
in a rootless container). -
We explicitly turned on the packet forwarding kernel parameter.
-
We added the
Z
option to the WireGuard config volume. -
We qualified the image name with the
docker.io
registry.
Use for 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:
-
We added the
Z
option to the WireGuard config volume. -
We qualified the image name with the
docker.io
registry.
Use for 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 |
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
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 |
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:
-
We added the
Z
option to the WireGuard config volume. -
We qualified the image name with the
docker.io
registry.
Use for Bridge Network
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:
-
We won’t set the “IP forwarding” kernel parameter in the WireGuard configuration (we’ll set it instead in the Podman configuration).
-
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 |
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:
-
We granted the
NET_RAW
capability (to runiptables
in a rootless container). -
We explicitly turned on the packet forwarding kernel parameter.
-
We added the
Z
option to the WireGuard config volume. -
We qualified the image name with the
docker.io
registry.
Use for Bridge Network 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:
-
Install the
iproute2
package in the container (or, ideally, the container’s image). -
Start up the container with the
NET_ADMIN
capability. -
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:
-
Create a custom image with a custom Dockerfile in which you install the
iproute2
package. -
Run the image with a custom script command (and with the
--cap-add NET_ADMIN
flag). -
As the first part of the script, run the
ip route add
command. -
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 |
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:
-
We explicitly turned on the packet forwarding kernel parameter.
-
We added the
Z
option to the WireGuard config volume. -
We qualified the image name with the
docker.io
registry.
Use for Bridge Network With WireGuard on 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 |
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 ( |
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
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 |
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
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 |
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).
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:
-
Turn on the
net.ipv4.conf.all.src_valid_mark
kernel parameter. -
Grant the WireGuard container the
NET_RAW
capability. -
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
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.