
Route traffic across Podman networks with Traefik
April 30, 2025
If you’ve followed my posts over the years, you know I prefer clean solutions to less clean ones for my home lab (more to come on this!). Over the past year, I settled on a pattern that gives me the isolation of Kubernetes Namespaces without any of its weight: one private Podman network per application, plus Traefik in a shared “DMZ” network that terminates TLS and forwards traffic where it needs to go.
The first step is to create the main Podman network that will be used as a front network.
You can also use Podman’s default network, but I prefer to explicitly create this network to ensure that there is no risk of it not being present.
To do so, you need to create a file in /etc/containers/systemd/dmz.network
with the following content:
[Unit]
Description=DMZ Network
[Network]
Driver=bridge
Internal=false
After having created it, remember to reload the Systemd daemons with the following:
systemctl daemon-reload
It is now possible to start it and enable it with:
systemctl enable --now dmz-network.service
There is a peculiarity about the name that took me a while to guess, but the network will automatically get the -network
suffix, so make sure not to put it in the unit file, but remember to add it in the commands.
Now that we have this first network, we can create the Traefik container by populating the /etc/containers/systemd/traefik.container
file with this content:
[Unit]
Description=Traefik
After=local-fs.target
[Install]
WantedBy=default.target
[Container]
ContainerName=traefik
Image=docker.io/library/traefik:3.3.5
Network=dmz.netowrk
PublishPort=80:80/tcp
PublishPort=443:443/tcp
Volume=/run/podman/podman.sock:/var/run/docker.sock:z
Volume=/var/srv/traefik/letsencrypt:/letsencrypt:Z
Environment=TRAEFIK_PROVIDERS_DOCKER=true
Environment=TRAEFIK_PROVIDERS_DOCKER_EXPOSEDBYDEFAULT=false
Environment=TRAEFIK_ENTRYPOINTS_WEB_ADDRESS=:80
Environment=TRAEFIK_ENTRYPOINTS_WEBSECURE_ADDRESS=:443
Sysctl=net.ipv4.ip_unprivileged_port_start=0
SecurityLabelDisable=true
Note that in this example, I removed all TLS-related environmental variables, but if you want to use this capability, you will need to set the right ones based on how Traefik should obtain the TLS certificates in your environment.
It is also important to notice the parameters SecurityLabelDisable=true
, Volume=/run/podman/podman.sock:/var/run/docker.sock:z
, and Environment=TRAEFIK_PROVIDERS_DOCKER=true
which, all together, allow Traefik to properly query the Podman service to get information about the running containers and their labels.
Remember to enable the Podman API daemon since Traefik queries Podman over the REST socket to fetch container labels.
By default, Podman does not run the API daemon, so it is critical to start it, and ed enable it:
systemctl enable --now podman.socket
The socket activates podman.service
on demand and binds at unix:///run/podman/podman.sock
(or $XDG_RUNTIME_DIR/podman/podman.sock
rootless).
It is now possible to reload the Systemd daemons and start and enable the Traefik service.
systemctl daemon-reload
systemctl enable --now traefik.service
We can now proceed with creating the various services. I’ll show a simple one as an example, but you can obviously add multiple ones.
In this example, we will create a whoami
service and publish it as https://whoami.example.com
.
The first step is to create a Podman network for the service in /etc/containers/systemd/whoami.network
:
[Network]
Driver=bridge
Internal=true
We can now proceed with the definition of the container itself in /etc/containers/systemd/whoami.container
[Unit]
Requires=whoami.network dmz.network
After=whoami.network dmz.network
[Container]
Name=whoami
Image=docker.io/library/whoami:v1.11.0
Network=whoami.network
Network=dmz.network
Label=traefik.enable=true
Label=traefik.docker.network=dmz
Label=traefik.http.routers.whoami.rule=Host(`whoami.example.com`)
Label=traefik.http.routers.whoami.entrypoints=websecure
Label=traefik.http.routers.whoami.tls=true
Label=traefik.http.services.whoami.loadbalancer.server.port=80
As you can notice, we defined the Network
key twice.
This means that this container will get two IPs, one for each of those networks.
Be aware that order matters because the first one will be the defatult one.
We have also set a number of labels that Traefik will use to configure the routes and the TLS certificates properly.
The last important thing to notice is that the Unit
is declared to be run after whoami.network
and dmz.network
and to require them.
This detail ensures that Systemd will not try to execute this container before the networks are properly set up.
If we have other containers that are part of the same application, like a database, a backend service, etc., you can add more .container
files, with the difference that they only join the whoami
network and do not have any Traefik label.
It is now possible to reload the Systemd daemons and start and enable the Whoami network and service.
systemctl daemon-reload
systemctl enable --now whoami-network.service
systemctl enable --now whoami.service
Now, Traefik will be aware of this new service and will properly configure itself to route the traffic.
In this example, I showed the files and commands as if they were manually executed, but this is just for clarity’s sake. The reality is that I have all those files and commands fully managed by Ansible, and if you know a bit about Ansible, you probably have a clear idea of how to do so since those are very basic operations.
There are a couple of points that I really like about this approach.
The first is that there is hard(er) isolation between applications since every stack lives in its own bridge network. So containers that have no business talking to each other simply can’t. That keeps lateral movement attacks at bay.
The second aspect is that there is no per-app Traefik configuration within Traefik. Traefik watches the Podman API state and picks up routing rules from container labels, so spinning up a new service never forces me to touch traefik.yml
or restart the proxy.