Podman Networking Docs
This guide is about how to configure networking when using rootless Podman.
Inbound TCP/UDP connections
Overview
Listening TCP/UDP sockets
method | source address preserved | native perfomance | support for binding to specific network device | minimum port number |
---|---|---|---|---|
socket activation (systemd user service) | ✔️ | ✔️ | ✔️ | ip_unprivileged_port_start |
socket activation (systemd system service with User=) | ✔️ | ✔️ | ✔️ | 0 |
pasta | ✔️ | ✔️ | ip_unprivileged_port_start | |
slirp4netns + port_handler=slirp4netns | ✔️ | ip_unprivileged_port_start | ||
slirp4netns + port_handler=rootlesskit | ip_unprivileged_port_start | |||
host | ✔️ | ✔️ | ✔️ | ip_unprivileged_port_start |
Source address preserved
method | source address preserved |
---|---|
socket activation (systemd user service) | ✔️ |
socket activation (systemd system service) | ✔️ |
pasta | ✔️ |
slirp4netns + port_handler=slirp4netns | ✔️ |
slirp4netns + port_handler=rootlesskit | |
host | ✔️ |
Example:
If the source address is preserved in the incoming TCP connection, then nginx is able to see the IP address of host2 (192.0.2.10) where the curl request is run.
nginx logs the HTTP request as coming from 192.0.2.10
192.0.2.10 - - [15/Jun/2023:07:41:18 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/8.1.1" "-"
If the source address is not preserved, then nginx sees another source address in the TCP connection. For example, if the nginx container is run with slirp4netns + port_handler=rootlesskit
podman run --network=slirp4netns:port_handler=rootlesskit \
--publish 8080:80 \
--rm \
ghcr.io/nginxinc/nginx:latest
nginx logs the HTTP request as coming from 10.0.2.2
10.0.2.2 - - [15/Jun/2023:07:41:18 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/8.1.1" "-"
example: socket activation (systemd user service) - source address preserved
Click me
This example uses two computers
- host1.example.com (for running the nginx web server)
- host2.example.com (for running curl)
On host1 create user test
sudo useradd test
Open an interactive shell session for the user test
sudo machinectl shell test@
Create directories
mkdir -p ~/.config/containers/systemd
mkdir -p ~/.config/systemd/userCreate the file /home/test/.config/containers/systemd/nginx.container containing
[Container]
Image=ghcr.io/nginxinc/nginx-unprivileged:latest
ContainerName=mynginx
Environment="NGINX=3;"
[Install]
WantedBy=default.targetCreate the file /home/test/.config/systemd/user/nginx.socket containing
[Unit]
Description=nginx socket
[Socket]
ListenStream=0.0.0.0:8080
[Install]
WantedBy=default.targetReload the systemd user manager
systemctl --user daemon-reload
Pull the container image
podman pull ghcr.io/nginxinc/nginx-unprivileged:latest
Start the socket
systemctl --user start nginx.socket
Test the nginx web server by accessing it from host2
- Log in to host2
- Run curl
curl host1.example.com:8080
- Log out from host2
Check the logs in the container mynginx
podman logs mynginx 2> /dev/null | grep "GET /"
The output should look something like
192.0.2.10 - - [15/Jun/2023:07:41:18 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/8.1.1" "-"
nginx logged the source address of the TCP connection to be 192.0.2.10 which matches the IP address of host2.example.com. Conclusion: the source address was preserved.
A side-note: If the feature request https://trac.nginx.org/nginx/ticket/237 gets implemented,
the Environment="NGINX=3;"
could be removed. This example makes use of the fact that "nginx includes an undocumented,
internal socket-passing mechanism" quote from https://freedesktop.org/wiki/Software/systemd/DaemonSocketActivation/
example: pasta - source address preserved
Click me
This example uses two computers
- host1.example.com (for running the nginx web server)
- host2.example.com (for running curl)
On host1 create user test
sudo useradd test
Open an interactive shell session for the user test
sudo machinectl shell test@
Create directories
mkdir -p ~/.config/containers/systemd
Create the file /home/test/.config/containers/systemd/nginx.container containing
[Container]
Image=ghcr.io/nginxinc/nginx-unprivileged:latest
ContainerName=mynginx
Network=pasta
PublishPort=0.0.0.0:8080:8080
[Install]
WantedBy=default.targetReload the systemd user manager
systemctl --user daemon-reload
Pull the container image
podman pull ghcr.io/nginxinc/nginx-unprivileged:latest
Start the service
systemctl --user start nginx.service
Test the nginx web server by accessing it from host2
- Log in to host2
- Run curl
curl host1.example.com:8080
- Log out from host2
Check the logs in the container mynginx
podman logs mynginx 2> /dev/null | grep "GET /"
The output should look something like
192.0.2.10 - - [15/Jun/2023:07:55:03 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/8.1.1" "-"
nginx logged the source address of the TCP connection to be 192.0.2.10 which matches the IP address of host2.example.com. Conclusion: the source address is preserved.
example: slirp4netns + port_handler=slirp4netns - source address preserved
Click me
Follow the same steps as
example: pasta - source address preserved
but replace Network=pasta
with Network=slirp4netns:port_handler=slirp4netns
.
In other words, replace step 4 with
Create the file /home/test/.config/containers/systemd/nginx.container containing
[Container]
Image=ghcr.io/nginxinc/nginx-unprivileged:latest
ContainerName=mynginx
Network=slirp4netns:port_handler=slirp4netns
PublishPort=0.0.0.0:8080:8080
[Install]
WantedBy=default.target
example: slirp4netns + port_handler=rootlesskit - source address not preserved
Click me
Follow the same steps as
example: pasta - source address preserved
but replace Network=pasta
with Network=slirp4netns:port_handler=rootlesskit
.
In other words, replace step 4 with
Create the file /home/test/.config/containers/systemd/nginx.container containing
[Container]
Image=ghcr.io/nginxinc/nginx-unprivileged:latest
ContainerName=mynginx
Network=slirp4netns:port_handler=rootlesskit
PublishPort=0.0.0.0:8080:8080
[Install]
WantedBy=default.target
At step 9 you will see that the source address is not preserved. Instead of 192.0.2.10 (IP address for host1.example.com), nginx instead logs the IP address 10.0.2.100.
podman logs mynginx 2> /dev/null | grep "GET /"
The output should look something like
10.0.2.100 - - [15/Jun/2023:07:55:03 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/8.1.1" "-"
example: host - source address preserved
Click me
Follow the same steps as
example: pasta - source address preserved
but remove the line PublishPort=0.0.0.0:8080:8080
and replace Network=pasta
with Network=host
.
In other words, replace step 4 with
Create the file /home/test/.config/containers/systemd/nginx.container containing
[Container]
Image=ghcr.io/nginxinc/nginx-unprivileged:latest
ContainerName=mynginx
Network=host
[Install]
WantedBy=default.target
Performance
method | native perfomance |
---|---|
socket activation (systemd user service) | ✔️ |
socket activation (systemd system service) | ✔️ |
pasta | |
slirp4netns + port_handler=slirp4netns | |
slirp4netns + port_handler=rootlesskit | |
host | ✔️ |
Best performance has
- socket activation (systemd user service)
- socket activation (systemd system service)
- host
where there is no slowdown compared to running directly on the host.
The other methods ordered from fastest to slowest:
- pasta
- slirp4netns + port_handler=rootlesskit
- slirp4netns + port_handler=slirp4netns
Support for binding to specific network device
method | support for binding to specific network device |
---|---|
socket activation (systemd user service) | ✔️ |
socket activation (systemd system service) | ✔️ |
pasta | ✔️ |
slirp4netns + port_handler=slirp4netns | |
slirp4netns + port_handler=rootlesskit | |
host | ✔️ |
examples
example: socket activation (systemd user service) - bind to specific network device
Click me
Specify the network device to bind to with the systemd directive BindToDevice in the socket unit file.
For example, to bind to the ethernet interface eth0, add the line
BindToDevice=eth0
The socket unit file could look like this
[Unit]
Description=example socket
[Socket]
ListenStream=0.0.0.0:8080
BindToDevice=eth0
[Install]
WantedBy=default.target
example: pasta - bind to specific network device
Click me
To publish the TCP port 8080 and bind the listening socket to the ethernet interface eth0 use the configuration lines
Network=pasta:-t,0.0.0.0%eth0/8080
PublishPorts=0.0.0.0:8080:8080
under the [Container]
section in the container file.
For example the file /home/test/.config/containers/systemd/nginx.container containing
[Container]
Image=ghcr.io/nginxinc/nginx-unprivileged:latest
ContainerName=mynginx
Network=pasta:-t,0.0.0.0%eth0/8080
PublishPort=0.0.0.0:8080:8080
[Install]
WantedBy=default.target
If you want to publish an UDP port instead of a TCP port, replace -t
with -u
above.
Configure ip_unprivileged_port_start
Read the current setting
$ cat /proc/sys/net/ipv4/ip_unprivileged_port_start
1024
To set a new value (for example 443), create the file /etc/sysctl.d/99-mysettings.conf with the contents:
net.ipv4.ip_unprivileged_port_start=443
and reload the configuration
sudo sysctl --system
The setting is system-wide so changing it impacts all users on the system.
Outbound TCP/UDP connections
Outbound TCP/UDP connections to the internet
An example of an outbound TCP/UDP connection to the internet is when a container downloads a file from a web server on the internet.
method | native perfomance |
---|---|
pasta | |
slirp4netns | |
host | ✔️ |
Outbound TCP/UDP connections to the host's localhost
An example of an outbound TCP/UDP connection to the host's localhost is when a container downloads a file from a web server on the host that listens on 127.0.0.1:80.
method | outbound TCP/UDP connection to the host's localhost allowed by default |
---|---|
pasta | |
slirp4netns | |
host | ✔️ |
Connecting to the host's localhost is not enabled by default for pasta and slirp4netns due to security reasons.
See network mode host
as to why access to the host's localhost is considered insecure.
To allow curl in a container to connect to a web server on the host that listens on 127.0.0.1:80,
for pasta add the option --map-gw
podman run --rm \
--network=pasta:--map-gw \
registry.fedoraproject.org/fedora curl localhost 10.0.2.2:80
and for slirp4netns add the option slirp4netns:allow_host_loopback=true
podman run --rm \
--network=slirp4netns:allow_host_loopback=true \
registry.fedoraproject.org/fedora curl localhost 10.0.2.2:80
Connecting to Unix socket on the host
method | description |
---|---|
systemd directive OpenFile= | The executed command in the container inherits a file descriptor to an already opened file. |
bind-mount, (--volume ./dir:/dir:Z ) | Standard way |
The systemd directive OpenFile=
was
introduced in systemd 253 (released February 2023).
See also Unix Sockets
Valid method combinations
The methods
- pasta
- slirp4netns + port_handler=rootlesskit
- slirp4netns + port_handler=slirp4netns
- host
are mutually exclusive.
Socket activation can be combined with the other methods.
Description of the different methods
Socket activation (systemd user service)
This method can only be used for container images that has software that supports socket activation.
Socket activation of a systemd user service is set up by creating two files
- ~/.config/systemd/user/example.socket
and either a Quadlet file
- ~/.config/containers/systemd/example.container
or a service unit
- ~/.config/systemd/user/example.service
Socket activation (systemd system service with User=
)
⚠️ Running Podman in a systemd system service with User=
is not
yet supported (see feature request https://github.com/containers/podman/issues/12778)
but if you are willing to experiment (that is to patch and rebuild Podman), you might get this to work.
Although root privileges are required to create a system system service, note that rootless Podman is being used
when User=
is set under the [Service]
section in the service unit file.
Socket activation of a systemd system service is set up by creating two files
- /etc/systemd/system/example.socket
- /etc/systemd/system/example.service
and running
systemctl daemon-reload
systemctl start example.socket
and possibly
systemctl enable example.socket
to start the socket automatically after a reboot.
See also https://github.com/containers/podman/issues/12778#issuecomment-1586255815
which describes a successful experiment to run Podman in a systemd system service
with User=
. The experiment depends on a patched Podman. The suggested approach also needs to
add more security checks to be safe.
Pasta
Pasta is similar to Slirp4netns. Pasta is generally the better choice because it is often faster and has more features than slirp4netns.
See the --network
option.
See also the pasta web page https://passt.top/
Slirp4netns
slirp4netns is enabled by default if no --network
option is provided to podman run
.
The two port forwarding modes allowed with slirp4netns are described in https://news.ycombinator.com/item?id=33255771
See the --network
option.
Host
⚠️ Using --network=host
is considered insecure.
Quote from podman run man page: "The host mode gives the container full access to local system services such as D-bus and is therefore considered insecure".
See also the article [CVE-2020–15257] Don’t use --net=host . Don’t use spec.hostNetwork that explains why running containers in the host network namespace is insecure.