Podman es un motor de contenedores OCI que no necesita ningún proceso central ejecutándose en segundo plano para funcionar. Cada invocación de podman run crea el contenedor directamente como proceso hijo del shell que lo lanzó, sin intermediarios. Esto contrasta fundamentalmente con Docker, donde el cliente solo envía instrucciones a dockerd, un daemon que corre como root y que tiene control total sobre el sistema. Con Podman, si matas el terminal, el contenedor muere con él —a menos que lo delegues a systemd, que es exactamente lo que harás en producción.
El punto más importante de seguridad: rootless significa que el proceso que gestiona el contenedor pertenece a tu usuario, no a root. Podman usa los user namespaces del kernel de Linux para mapear un rango de UIDs del sistema a UIDs dentro del contenedor. Tu usuario deploy con UID 1000 puede ejecutar un contenedor donde el proceso interno cree que es root (UID 0), pero desde fuera del contenedor ese proceso tiene solo los privilegios de UID 1000. Si el contenedor se escapa —vulnerabilidad en el runtime, mala configuración— el atacante obtiene un usuario sin privilegios, no root. Con el daemon Docker tradicional, una fuga de contenedor históricamente equivalía a una escalada de privilegios directa.
¿Cuándo usas Podman en lugar de Docker? Cuando el entorno de destino exige que no haya un daemon privilegiado corriendo, que es el caso en muchos entornos corporativos, en servidores compartidos, y en cualquier sitio donde el equipo de seguridad haya leído el CVE-2019-5736. También cuando trabajas con systemd directamente y quieres que los contenedores sean unidades del sistema como cualquier otro servicio. En desarrollo personal la diferencia es menos crítica, pero construir el hábito de rootless desde el principio evita sorpresas al desplegar.
Lo que se rompe si no entiendes el modelo: los bind mounts tienen comportamiento diferente en rootless porque el mapeo de UIDs puede hacer que los ficheros del host aparezcan como nobody dentro del contenedor. Los puertos por debajo de 1024 requieren configuración adicional en el kernel (net.ipv4.ip_unprivileged_port_start). Y si intentas usar podman generate systemd sin entender si el contenedor debe vivir en el contexto del usuario o del sistema, acabas con servicios que no arrancan en el boot.
Instalación y ejemplo completo
# Instalación en Debian Bookworm sudo apt install podman podman-compose # Verificar que funciona en modo rootless (sin sudo) podman info | grep -E "rootless|cgroupVersion" # Confirmar que tenemos subordinate UIDs asignados a nuestro usuario # Podman necesita este rango para construir el user namespace grep "$(whoami)" /etc/subuid /etc/subgid # Si el usuario no aparece, añadirlo (reemplaza 'deploy' por tu usuario) # sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 deploy # ── Ejecutar un contenedor básico como usuario normal ────────────────── podman run --rm docker.io/library/alpine:3.19 id # Dentro del contenedor reporta uid=0(root), pero desde fuera es tu UID # ── Escenario real: nginx sirviendo contenido estático ───────────────── mkdir -p ~/containers/nginx-demo/html echo "<h1>Funciona sin root</h1>" > ~/containers/nginx-demo/html/index.html podman run -d \ --name nginx-demo \ -p 8080:80 \ -v ~/containers/nginx-demo/html:/usr/share/nginx/html:ro,Z \ docker.io/library/nginx:1.25-alpine # La opción :Z es crítica en sistemas con SELinux/Apparmor: # le pide a Podman que reetiquete el volumen para que el contenedor pueda leerlo. # En Debian sin SELinux activo no cambia nada, pero el hábito evita roturas en RHEL. # Verificar que el contenedor está corriendo podman ps # Ver los logs (misma interfaz que docker logs) podman logs nginx-demo podman logs -f nginx-demo # seguir en tiempo real # Comprobar que sirve curl -s http://localhost:8080 # ── Generar un unit file de systemd para este contenedor ────────────── mkdir -p ~/.config/systemd/user/ # Genera el unit para el usuario actual (no sistema) podman generate systemd \ --name nginx-demo \ --restart-policy=always \ --files \ --new \ -n nginx-demo # --new hace que el unit recree el contenedor en cada arranque del servicio # en lugar de simplemente hacer start/stop del contenedor existente. # Es más robusto: el contenedor siempre sale de una imagen limpia. mv container-nginx-demo.service ~/.config/systemd/user/ # Activar el linter para detectar problemas antes de habilitar systemd-analyze --user verify ~/.config/systemd/user/container-nginx-demo.service # Habilitar e iniciar como servicio de usuario systemctl --user daemon-reload systemctl --user enable --now container-nginx-demo.service # Para que el servicio de usuario arranque sin necesidad de login interactivo # (importante en servidores) sudo loginctl enable-linger "$(whoami)" # Estado del servicio systemctl --user status container-nginx-demo.service # ── Inspeccionar el mapeo de UIDs ────────────────────────────────────── # Ver qué PID tiene el proceso nginx en el host podman top nginx-demo huser hpid # Comparar: dentro del contenedor es root, fuera es tu usuario podman exec nginx-demo id ps aux | grep nginx | head -5 # ── Limpieza ─────────────────────────────────────────────────────────── systemctl --user disable --now container-nginx-demo.service podman rm -f nginx-demo podman rmi docker.io/library/nginx:1.25-alpine
Lo que acaba de pasar
El bloque de grep sobre /etc/subuid y /etc/subgid no es decorativo. Podman rootless no puede construir el user namespace sin ese rango de UIDs subordinados asignados a tu usuario. En Debian, cuando creas un usuario con adduser, los rangos se asignan automáticamente en /etc/subuid. Si trabajas con usuarios creados a mano con useradd o en sistemas más antiguos, es posible que no estén y podman run fallará con un error críptico sobre newuidmap.
La opción -p 8080:80 funciona porque 8080 está por encima del límite de puertos no privilegiados (1024 por defecto en Linux). Si necesitaras el puerto 80 directamente, tendrías que bajar ese límite con sysctl -w net.ipv4.ip_unprivileged_port_start=80 o usar un proxy delante.
El comportamiento de podman generate systemd --new merece atención. Sin --new, el unit generado hace podman start/podman stop sobre el contenedor que ya existe en el almacenamiento local. Con --new, el unit ejecuta podman run completo en cada arranque del servicio, lo que significa que siempre parte de la imagen limpia y no depende de que el contenedor siga existiendo en el almacenamiento. En producción, --new es casi siempre la opción correcta porque hace el servicio idempotente.
loginctl enable-linger es la pieza que la mayoría olvida. Los servicios de usuario de systemd normalmente solo corren mientras hay una sesión activa. linger le dice a systemd que mantenga esa “sesión de usuario” viva aunque no haya ningún login, de modo que container-nginx-demo.service arranque en el boot igual que cualquier servicio del sistema.
El podman top nginx-demo huser hpid al final muestra la dualidad en acción: la columna huser es el usuario en el host, que es tu cuenta sin privilegios, mientras que dentro del contenedor podman exec nginx-demo id reporta uid=0. Eso es exactamente el user namespace funcionando: el kernel mantiene la traducción transparente, y si algo explota dentro de ese contenedor, lo que sale al host es un proceso con tus permisos de usuario, no con los de root.
[Ubuntu]: Podman está disponible directamente en apt desde Ubuntu 22.04. En versiones anteriores era necesario añadir el repositorio de Kubic. El comportamiento rootless es idéntico.