778 - ¡Adiós Docker! Cómo configurar Traefik con Podman (Rootless y Seguro)
Aprende a configurar Traefik con Podman usando Quadlets. Guía completa sobre puertos rootless, persistencia en Systemd, seguridad avanzada y HTTP/3.
Como configurar Traefik en Podman, ha sido probablemente una de las preguntas mas recurrentes desde que empecé con esta serie de episodios dedicados a Podman. Lo cierto, es que, en ningún momento me habría planteado realizar un migración tan radical como esta sin saber que mi proxy de cabecera iba a tener soporte. Pero si, lo tiene, y es tan sencillo como te podrías haber imaginado. Simplemente se trata de que el socket de Podman esté en funcionamiento y algunos detalles mas que he ido desgranando a lo largo de estos episodios. Así, el objetivo de este episodio, es básicamente dejar funcionando Traefik con Podman, para que puedas utilizarlo como tu proxy de cabecera, y así poder gestionar tus servicios de una manera sencilla y eficiente. Por supuesto como un Quadlet cualquiera.

¡Adiós Docker! Cómo configurar Traefik con Podman (Rootless y Seguro)
Preliminares
Los puertos 80 y 443
Antes de meternos con Traefik, hay que tener en cuenta que estoy ejecutando Podman en modo rootless, es decir, sin privilegios de superusuario. Esto es algo que me gusta mucho, porque me permite ejecutar mis contenedores sin necesidad de tener permisos de administrador, lo que aumenta la seguridad y la flexibilidad. Sin embargo, esto también implica que el socket de Podman no está disponible para todos los usuarios, sino que está limitado al usuario que lo ejecuta. Por lo tanto, para que Traefik pueda comunicarse con Podman, es necesario configurar el socket de manera adecuada.
Por otro lado, al no ser un usuario con privilegios, directamente no puedo utilizar el puerto 80 ni el 443. Es necesario realizar cambios. Por esta razón la primera operación que tienes que hacer para poder utilizar estos dos puestos es ejecutando la siguiente instrucción,
echo "net.ipv4.ip_unprivileged_port_start=80" | sudo tee /etc/sysctl.d/rootless-ports.conf
sudo sysctl -p /etc/sysctl.d/rootless-ports.conf
Persistencia de usuario
Por defecto, Systemd mata los procesos de un usuario cuando este cierra la sessión. Para que tus contenedores sigan corriendo y a se inicien automáticamente al arrancar el sistema, es necesario configurar Systemd para que no mate los procesos de tu usuario. Para ello, puedes ejecutar la siguiente instrucción,
loginctl enable-linger $USER
El socket de Podman
Para utilizar el socket de Podman en modo rootless (como usuario normal), el proceso es similar al de los Quadlets, pero enfocado en el servicio de API de Podman. Para activar el socket de usuario,
systemctl --user enable --now podman.socket
Indicarte que el socket de Podman no se encuentra en /, si no que se encuentra normalmente en /run/user/${UID}/podman/podman.sock. De cualquier forma, puedes ver la ubicación exacta del socket ejecutando el siguiente comando,
podman info | grep socket
Existen determinadas herramientas que tiran del socket de Docker. Para que esas herramientas sepan donde buscar, simplemente tienes que configurar la variable DOCKER_HOST con la ruta del socket de Podman. Para ello, puedes ejecutar la siguiente instrucción,
export DOCKER_HOST=unix://${XDG_RUNTIME_DIR}/podman/podman.sock
En el caso del quadlet como veremos a continuación, la forma de hacerlo es la siguiente,
[Container]
Image=docker.io/library/traefik:latest
# Mapeas el socket del host al interior del contenedor
Volume=%t/podman/podman.sock:/var/run/docker.sock:Z
%t. Es un especificador de Quadlet que apunta automáticamente al «Runtime directory» del usuario (/run/user/$UID).:Z:. Es vital en sistemas como Fedora o RHEL para que SELinux permita el acceso al socket.
Los archivos de Traefik
Por la parte de Podman
Por la parte de podman, he definido 4 Quadlets,
traefik.container. Define el contenedor de Traefik, con la imagen que vamos a utilizar, el mapeo del socket de Podman, y la configuración de los puertos 80 y 443.traefik-certs.volume. Define un volumen para almacenar los certificados de Traefik, que se mapea al interior del contenedor.traefik-plugins.volume. Define un volumen para almacenar los plugins de Traefik, que se mapea al interior del contenedor.proxy.network. Define una red personalizada para que los contenedores puedan comunicarse entre sí. Esta red la utilizaremos en el resto de contenedores que necesiten ser accesisbles desde el mundo exterior.
Del traefik.container, que está disponible en GitHub, quiero destacar algunos puntos que me parecen realmente importantes.
- Los puertos. He declarado el 443/udp además del tcp y del 80. Esto es para poder habilitar HTTP/3, que es el protocolo mas moderno y eficiente para la comunicación entre el cliente y el servidor. HTTP/3 utiliza QUIC como protocolo de transporte, que a su vez utiliza UDP en lugar de TCP. Por lo tanto, para habilitar HTTP/3 en Traefik, es necesario exponer el puerto 443 tanto para TCP como para UDP.
- El socket, como indiqué anteriormente
Volume=%t/podman/podman.sock:/var/run/docker.sock:ro
En este caso, como HTTP/3 usa UDP de forma intensiva, el kernel de Linux puede ser un cuello de botella con los tamaños de búfer por defecto. Para que evitarlo, crea el archivo /etc/sysctl.d/99-traefik-http3.conf, con el siguiente contenido,
net.core.rmem_max=2500000
net.core.wmem_max=2500000
Aplica los cambios con sudo sysctl --system
Y por otro lado están los archivos de configuración estática y dinámica de Traefik. Realmente, estos los podría haber metido en un volumen, pero como es algo que habitualmente modifico, lo he dejado disponible en el host para modificarlos cuando me rote.
Aquí me planteé donde guardarlos, y en seguida caí en la cuenta que, al fin y al cabo, son archivos de configuración, dotfiles, con lo que su sitio ideal es en ~/.config/traefik. Y lo mismo para el resto de contenedores que necesiten tener una configuración personalizada, lo ideal es que esa configuración esté disponible en el host para poder modificarla de forma sencilla, y luego mapearla al interior del contenedor.
El Quadlet
Para levantar Traefik en Podman, he creado un Quadlet ~/.config/containers/systemd/traefik.container con el siguiente contenido,
[Unit]
Description=Traefik Edge Router (Rootless)
After=network-online.target
[Container]
Image=docker.io/library/traefik:latest
ContainerName=traefik
# Puertos (Recuerda haber bajado el net.ipv4.ip_unprivileged_port_start a 80)
PublishPort=80:80
PublishPort=443:443/tcp
PublishPort=443:443/udp
# 1. Archivo de configuración estática
Volume=%h/.config/traefik/traefik.yml:/etc/traefik/traefik.yml:ro,Z
# 2. Volumen de Podman para certificados (Gestionado por Podman, no bind-mount)
Volume=traefik-certs.volume:/etc/traefik/certs:Z
# 3. Socket de Podman del usuario para el discovery
Volume=%t/podman/podman.sock:/var/run/docker.sock:ro
# 4. Directorio dinámico opcional
Volume=%h/.config/traefik/dynamic:/etc/traefik/dynamic:ro,Z
# 5. Persistencia de Plugins
Volume=traefik-plugins.volume:/plugins-storage:rw,Z
Network=proxy.network
# Definición del Healthcheck
HealthCmd=traefik healthcheck --ping --ping.address=:8082
HealthInterval=30s
HealthRetries=3
HealthTimeout=5s
HealthStartPeriod=10s
# Hardening
# 1. No permitir escalada de privilegios y limitamos recursos.
PodmanArgs=--security-opt=no-new-privileges
# 2. Hacer el sistema de archivos del contenedor de solo lectura
# (Traefik solo necesita escribir en /etc/traefik/certs y /plugins-storage que son volúmenes)
ReadOnly=true
# 3. Eliminar capacidades de root innecesarias
# Traefik solo necesita NET_BIND_SERVICE para los puertos 80/443
DropCapability=all
AddCapability=cap_net_bind_service
[Service]
Environment=SOPS_AGE_KEY_FILE=%h/.secrets/sops/age/key.txt
Environment=PATH=/usr/local/bin:/usr/bin:%h/.local/bin
Restart=always
# 4. Evita ataques DoS que consuman RAM/CPU
MemoryLimit=512M
CPUWeight=50
[Install]
WantedBy=default.target
De este container quiero destacarte algunos aspectos, que como te decía anteriormente son realmente interesantes.
- HTTP/3. Para poder utilizar
HTTP/3he mapeado el puerto443tantoTCPcomoUDP, dado que se utiliza el protocolo QUIC que está basado enUDP. Ahora solo falta que no me olvide de abrir en el firewall ambos puertos, porque si no habré hecho un pan como una torta. - Rootless con privilegios mínimos. Como ya sabes, estoy ejecutando Traefik con privilegios mínimos pero además con
DropCapability=allyAddCapability=cap_net_bind_service, lo que significa que el contenedor no tiene capacidades de root innecesarias, pero sí tiene la capacidad necesaria para enlazar los puertos 80 y 443, que he agregado explícitamente. Por otro lado conno-new-privilegesme aseguro de que el contenedor no pueda escalar privilegios, lo que añade una capa adicional de seguridad. Pero además conReadOnly=truehago que el sistema de archivos del contenedor sea de solo lectura, lo que limita aún más las posibilidades de un atacante en caso de que logre comprometer el contenedor. Esto es especialmente importante para un proxy de borde como Traefik, que está expuesto a Internet y puede ser un objetivo atractivo para los atacantes. - Gestión de Volúmenes. He definido volúmenes específicos para los certificados y los plugins de Traefik, lo que permite una gestión más organizada y segura de estos datos. Además, el socket de Podman se mapea como solo lectura, lo que garantiza que Traefik pueda comunicarse con Podman sin riesgo de modificar el socket. La idea e tener los plugins en un volumen es para evitar tener que descargarlos cada vez que inicio el contenedor
- Comunicación vía socket del usuario.
%sse expande a la ruta del Runtime Directory del usuario, normalmente a/run/user/1000. Al montar%t/podman/podman.sock, Traefik puede escuchar cuando se levanta otro Quadlet y crear las rutas automáticas, tal y como hace Docker, pero de forma completamente aislada dentro de la sesión de usuario. - Healthcheck. He definido un healthcheck para Traefik utilizando el comando
traefik healthcheck --ping --ping.address=:8082, lo que permite monitorear la salud del contenedor y reiniciarlo automáticamente si no responde correctamente. No solo esto, si no que tal y como expliqué en un episodio anterior, esto me permite que cuando se actualice, si la cosa no va bien se realice un rollback automático a la versión anterior, lo que es una gran ventaja en términos de estabilidad y confiabilidad. - Control de recursos. He definido límites de memoria y peso de CPU para evitar que un ataque de denegación de servicio (DoS) pueda consumir todos los recursos del sistema. Esto es especialmente importante para un proxy de borde como Traefik, que está expuesto a Internet y puede ser un objetivo atractivo para los atacantes.
_ Gestión de secretos. He configurado variables de entorno para la gestión de secretos con SOPS, lo que permite una integración segura y eficiente de secretos en Traefik.
Configuración estática
Traefik permite tener configuración estática y dinámica. La estática es la que se carga al iniciar el contenedor, y la dinámica es la que se puede modificar en caliente sin necesidad de reiniciar el contenedor. En este caso, la configuración estática la he dejado en ~/.config/traefik/traefik.yml, con el siguiente contenido,
# Configuración Estática
# Desactivar protocolos TLS antiguos (Solo TLS 1.2 y 1.3)
tls:
options:
default:
minVersion: VersionTLS12
cipherSuites:
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
- TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
- TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
- TLS_AES_128_GCM_SHA256
- TLS_AES_256_GCM_SHA384
- TLS_CHACHA20_POLY1305_SHA256
api:
dashboard: true
insecure: false
ping:
entryPoint: healthcheck
entryPoints:
web:
address: ":80"
http:
redirections:
entryPoint:
to: websecure
scheme: https
permanent: true
websecure:
address: ":443"
http3:
advertisedPort: 443
# Optimización: Mantener conexiones abiertas para mejorar performance
transport:
respondingTimeouts:
readTimeout: 60s
writeTimeout: 60s
idleTimeout: 180s
healthcheck:
address: ":8082"
providers:
docker:
endpoint: "unix:///var/run/docker.sock"
exposedByDefault: false
file:
directory: /etc/traefik/dynamic
watch: true
certificatesResolvers:
myresolver:
acme:
email: tu-email@atareao.es
storage: /etc/traefik/certs/acme.json
tlsChallenge: {}
log:
level: INFO
format: common
accessLog:
# Al no poner filePath, sale por stdout/stderr
format: json
bufferingSize: 100
fields:
names:
StartLocal: keep # Útil para tener la hora local del VPS
experimental:
plugins:
traefik-oidc-auth:
moduleName: "github.com/sevensolutions/traefik-oidc-auth"
version: "v0.11.0"
De esta configuración estática quiero destacar algunos puntos,
- tls. He indicado una versión mínima de TLS. Esto elimina vectores de ataque antiguos y ataques tipo Man-in-the-Middle que podrían aprovechar vulnerabilidades en versiones anteriores de TLS. Además, he especificado un conjunto de cipher suites modernas y seguras, lo que garantiza que las conexiones cifradas sean robustas y resistentes a ataques conocidos. Además está incluido
TLS_CHACHA20_POLY1305_SHA256que está especialmente pensado en dispositivos móviles. - api. El dashboard de Traefik se ha convertido en una pieza indispensable para mi a la hora de descubrir que está pasando en mi sistema, y para diagnosticar problemas. Por eso lo tengo habilitado, pero con
insecure: false, lo que significa que solo se puede acceder a él a través de HTTPS, y no a través de HTTP. - ping. Es un punto de entrada que se utiliza para realizar comprobaciones de salud (health checks) en Traefik. Esto es especialmente útil para asegurarse de que Traefik está funcionando correctamente y para integrarlo con sistemas de monitoreo o balanceadores de carga.
respondingTimeouts. Esta configuración es una optimización para mantener las conexiones abiertas durante más tiempo, lo que puede mejorar el rendimiento en situaciones de alta carga o cuando se utilizan protocolos como HTTP/3 que pueden beneficiarse de conexiones persistentes.
Configuración dinámica
Para la configuración dinámica, he creado el directorio ~/.config/traefik/dynamic, que se mapea al interior del contenedor en /etc/traefik/dynamic. En este directorio coloco archivos de configuración, que se cargan en caliente sin necesidad de reiniciar el contenedor. Actualmente tengo dos, uno en el que se encuentran dos middleware, para encabezados de seguridad y compresión, que los utilizo a discreción y según necesidad.
http:
middlewares:
compresionHeaders:
compress:
excludedContentTypes:
- text/event-stream
- application/x-xz
seguridadHeaders:
headers:
# HSTS (Fuerza HTTPS durante un año)
stsSeconds: 31536000
stsIncludeSubdomains: true
stsPreload: true
# Evitar Sniffing de contenido
contentTypeNosniff: true
# Evitar Clickjacking (Nadie puede meter tu web en un iframe)
frameDeny: true
# Filtro XSS básico
browserXssFilter: true
# Referrer Policy
referrerPolicy: "same-origin"
Y un segundo archivo en el que defino otro middleware para la autenticación con OIDC, que utiliza el plugin traefik-oidc-auth que he incluido en la configuración estática. Este plugin me permite proteger mis servicios con autenticación de OpenID Connect, lo que añade una capa adicional de seguridad a mis aplicaciones expuestas a Internet.
En este caso la configuración tiene algunas variables en formato jinja. Esto es así porque me permite tenerlo expuesto directamente en GitHub para que cualquiera lo pueda utilizar, y que se construye cuando sincronizo el repositorio. De todas formas, esto te lo explicaré con detalle en un siguiente episodio para contarte exactamente como lo estoy haciendo.
http:
middlewares:
oidc-auth:
plugin:
traefik-oidc-auth:
Secret: {{ env.OIDC_SECRET }}
Provider:
ClientId: {{ env.OIDC_CLIENT_ID }}
ClientSecret: {{ env.OIDC_CLIENT_SECRET }}
Url: https://auth.{{ env.FQDN }}
TokenValidation: IdToken
Scopes:
- openid
- profile
- email
routers:
dashboard:
rule: Host(`traefik.{{ env.FQDN }}`) # El subdominio para tu panel
service: api@internal
entryPoints:
- websecure
tls:
certResolver: myresolver
middlewares:
- oidc-auth
- compresionHeaders
- seguridadHeaders
Conclusión
Como ves, salgo el detalle de habilitar el socket de Podman y configurar los puertos, el resto es prácticamente igual a lo que haría con Docker. Esto es algo que me parece realmente interesante, porque significa que puedo migrar mis servicios de Docker a Podman sin tener que preocuparme por la compatibilidad con Traefik, lo que me da una gran flexibilidad y libertad a la hora de elegir las herramientas que quiero utilizar en mi stack de autoalojamiento.