Introducción

Un equipo de SRE necesita orquestar contenedores en servidores compartidos donde nadie tiene acceso root y el daemon de Docker genera problemas de permisos en CI/CD pipelines. La solución no pasa por pedir privilegios: Podman expone un socket UNIX-domain compatible con la API Docker que permite correr docker-compose como usuario sin privilegios, usando systemd –user y user namespaces de Linux. El punto crítico que muchos equipos desconocen es que la migración no es solo «instalar Podman y listo»: implica configurar el socket, mapear subuid/subgid, manejar el ciclo de vida del systemd session y resolver qué pasa con los contenedores cuando el usuario cierra sesión.

Qué ocurrió

Podman se posicionó como alternativa daemonless a Docker en Fedora 28 (2018) y hoy es el runtime predeterminado en RHEL 8+, Fedora 31+ y AlmaLinux/Rocky 8+. Su arquitectura elimina el daemon centralizado: cada contenedor se ejecuta como proceso hijo directo del usuario. La mayoría de los equipos ya lo conocen para podman run o podman build, pero la compatibilidad con docker-compose depende de un mecanismo menos documentado: el socket podman.socket activado vía systemctl –user, que expone la API Docker sobre un socket UNIX en ${XDG_RUNTIME_DIR}/podman/podman.sock.

Este socket responde al protocolo HTTP/REST de la Docker Engine API (v1.40+), lo que significa que docker-compose, docker SDKs y cualquier cliente que respete la variable DOCKER_HOST lo consumen sin modificación. No hay shim ni capa de traducción: el socket implementa los endpoints que compose invoca (/containers/create, /containers/{id}/start, /networks, /volumes). La versión mínima de Podman para compatibilidad total con docker-compose v2.x es la 4.0+, que introdujo mejoras significativas en el manejo de networks y volumes a través del socket.

Impacto para DevOps / Infraestructura / Cloud / Seguridad

Para equipos que gestionan runners de CI/CD en GitLab, Jenkins o GitHub Actions, correr docker-compose rootless elimina un vector de escalada de privilegios: si un pipeline ejecuta código malicioso dentro de un contenedor, el proceso del host no tiene UID 0 real. El root dentro del contenedor se mapea al UID del usuario del host (típicamente 1000), y los demás UIDs se asignan a rangos definidos en /etc/subuid y /etc/subgid (rangos por defecto: 100000-655359).

En entornos multi-tenant donde varios desarrolladores comparten una máquina de desarrollo o un servidor de staging, el aislamiento por usuario nativo de Podman rootless supera al modelo de Docker rootful, donde un solo daemon compartido expone todos los contenedores a cualquier usuario en el grupo docker. Un usuario comprometido en el grupo docker puede montar / del host dentro de un contenedor y escalar a root. Con Podman rootless, ese ataque no funciona porque el usuario no tiene acceso al socket de otro usuario.

El trade-off operativo: el socket depende de una sesión systemd activa. Si el usuario no tiene lingering habilitado y cierra la sesión SSH, los contenedores se detienen. Esto rompe workflows donde un desarrollador lanza un stack de compose, se desconecta y espera que siga corriendo.

Detalles técnicos

Activación del socket (ejecutar como usuario, NO como root):

systemctl –user enable –now podman.socket

Esto genera un unit file en ~/.config/systemd/user/podman.socket que crea el socket en ${XDG_RUNTIME_DIR}/podman/podman.sock. $XDG_RUNTIME_DIR es un tmpfs privado montado por systemd para cada usuario (típicamente /run/user/1000/).

Requisito de sesión systemd: El comando anterior falla si se ejecuta vía sudo porque sudo no crea una sesión systemd para el usuario objetivo. Para ejecutarlo en contexto de otro usuario, se necesita machinectl shell –uid=usuario (paquete systemd-container) o un login directo por TTY/SSH.

Configuración del cliente Docker/compose:

export DOCKER_HOST=unix://${XDG_RUNTIME_DIR}/podman/podman.sock

Agregar a ~/.bashrc o ~/.zshrc para persistencia. Con esto, tanto docker-compose up como docker compose up (plugin v2) se comunican con Podman.

Verificación:

curl –unix-socket ${XDG_RUNTIME_DIR}/podman/podman.sock http://localhost/v1.40/info

Persistencia post-logout (ejecutar como root):

loginctl enable-linger $(whoami)

Esto mantiene la sesión systemd del usuario activa (y por tanto el socket y los contenedores) después de cerrar la sesión.

Operaciones root-in-user-namespace:

podman unshare chown 1000:1000 ./volume-data
podman unshare
# dentro del shell: cd $(podman mount mi-contenedor)

podman unshare abre un shell con UID 0 dentro del user namespace del usuario, permitiendo manipular archivos con permisos de contenedor sin tocar el root real del host.

Desactivar Docker daemon si coexiste (como root):

systemctl stop docker.socket docker.service
systemctl disable docker.socket docker.service

Qué deberían hacer los administradores y equipos técnicos

1. Validar subuid/subgid antes de habilitar rootless. Ejecutar cat /etc/subuid y confirmar que el usuario tiene un rango asignado (mínimo 65536 UIDs). Si falta: usermod –add-subuids 100000-165535 –add-subgids 100000-165535 usuario. Sin esto, Podman rootless no puede mapear UID 0 y los contenedores fallan al montar volumes.

2. Habilitar linger para usuarios de CI/CD. En runners de GitLab o Jenkins donde compose stacks deben sobrevivir entre builds, ejecutar loginctl enable-linger runner-user como root. Sin esto, cada job que no mantiene la sesión SSH mata los contenedores en ejecución.

3. Setear DOCKER_HOST en el entorno del runner. En .gitlab-ci.yml, Jenkinsfile o systemd unit del runner, exportar la variable antes de invocar compose. Alternativa: crear un /etc/profile.d/podman-docker-host.sh con la exportación, aunque en contenedores de runner puede requerir configuración del entrypoint.

4. Versionar Podman ≥ 4.9.x y docker-compose ≥ 2.20. Las versiones anteriores del socket tenían bugs en el manejo de network_mode: service y en la resolución de DNS interno entre contenedores. Fedora 39 y RHEL 9.3+ incluyen Podman 4.9+ en repositorios principales.

5. Documentar la limitación de podman mount. Requiere ejecutar dentro de podman unshare. Si el equipo depende de docker cp para extraer logs o artefactos, verificar que el flujo equivalente (podman cp) funciona sin unshare, ya que no necesita acceso directo al filesystem del contenedor.

6. Monitorear el socket como servicio systemd. Agregar al dashboard de observabilidad: systemctl –user status podman.socket y alertar si el socket deja de estar activo. En entornos con SELinux en enforcing (RHEL/Fedora), verificar que no haya denegaciones en /var/log/audit/audit.log relacionadas con container_runtime_t y el socket.

Conclusión

Podman rootless con docker-compose no es un experimento: es una topología soportada desde hace años en Fedora, RHEL y Alpine, con compatibilidad probada contra la API Docker v1.40+. La barrera real no es técnica sino operativa: entender que el socket vive en la sesión systemd del usuario, que el aislamiento de UIDs depende de /etc/subuid, y que la persistencia requiere loginctl enable-linger. Para equipos que gestionan runners compartidos, pipelines de CI o ambientes multi-tenant, eliminar el daemon rootful de Docker reduce la superficie de ataque sin sacrificar la compatibilidad con las herramientas de orquestación que ya usan.

Fuentes

  • https://elou.world/en/tutorial/podman-docker-compose
  • https://fedoramagazine.org/
  • https://www.scmagazine.com/

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *