Podman vs Docker en un VPS: diferencias reales
Podman no usa daemon y funciona rootless por defecto. Comprueba qué cambia en un VPS alquilado: Compose, quadlets, puertos y permisos de volúmenes.
Qué diferencia real hay entre Podman y Docker
Podman y Docker ejecutan las mismas imágenes OCI (open container initiative) en un VPS, por lo que la elección no depende del software que pueda ejecutar. La diferencia está en el modelo de procesos. Docker ejecuta un daemon con privilegios root que controla todos los contenedores, y el comando docker es un cliente pequeño que solicita a ese daemon que realice el trabajo. Podman no tiene daemon: podman run inicia el contenedor como proceso hijo de quien lo haya invocado, con su propio usuario sin privilegios.
Todo lo demás se deriva de ese hecho. El inicio automático pasa a ser responsabilidad de systemd en lugar del daemon. La propiedad de los volúmenes atraviesa un espacio de nombres de usuario, por lo que el propietario que muestra ls -l en el host no es el propietario que ve el contenedor. Los puertos inferiores a 1024 no se pueden asociar hasta que cambie un ajuste del kernel. La CLI (interfaz de línea de comandos) de docker sigue funcionando mediante un wrapper, hasta que algo necesita el socket de Docker.
Sin daemon: qué se ejecuta realmente al iniciar un contenedor
En un host Docker, pstree -a muestra dockerd como root, containerd junto a él y un containerd-shim-runc-v2 por cada contenedor en ejecución. La aplicación es un proceso hijo de ese shim, y el shim es un proceso hijo del PID 1. Nada conecta el contenedor con el shell que lo inició. Si detiene el daemon, pierde el plano de control de todos los contenedores del equipo y, con el ajuste predeterminado live-restore desactivado, systemctl restart docker también reinicia los contenedores.
Podman no tiene un proceso equivalente. Inicie un contenedor y obtendrá un único proceso conmon (monitor del contenedor) que mantiene el proceso principal del contenedor y pertenece al usuario que ejecutó el comando.
podman run -d --name web -p 8080:80 docker.io/library/caddy:2
ps -o user,pid,ppid,args -C conmon
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080ps debería mostrar conmon ejecutándose con su usuario de inicio de sesión y no como root, y curl debería mostrar 200. Como ningún servicio central administra el contenedor, sudo apt upgrade podman no detiene nada que ya esté en ejecución, y si se bloquea el monitor de un contenedor, los demás no se ven afectados.
La ausencia del daemon también tiene un coste. Nada inicia los contenedores después de un reinicio. --restart=always de Docker es una promesa que el daemon cumple durante el arranque, y Podman la sustituye por systemd. Para eso sirve la sección sobre quadlet que aparece a continuación.
El socket es la otra parte del problema. /var/run/docker.sock es un endpoint de API (interfaz de programación de aplicaciones) propiedad de root, y cualquier proceso que pueda escribir en él puede iniciar un contenedor con privilegios que monte el sistema de archivos del host. Añadir un usuario al grupo docker concede a ese usuario acceso root por una vía más indirecta. Conviene leerlo junto con conceder a cada cuenta de servicio sólo el acceso que necesita. Podman no expone ningún socket salvo que se solicite, y el socket que obtiene pertenece a un único usuario en /run/user/<uid>/podman/podman.sock.
Instalar Podman en Ubuntu 24.04 y confirmar que rootless funciona realmente
sudo apt update
sudo apt install -y podman uidmap
podman --version
podman info | grep -i rootlessEl paquete uidmap proporciona newuidmap y newgidmap. Son los auxiliares setuid que permiten a un usuario normal reclamar un rango de ID subordinados. Sin ellos, los contenedores rootless no se inician. podman info debe mostrar rootless: true.
Ubuntu 24.04 incluye Podman 4.9 y Debian 13 incluye Podman 5.x, según la comprobación realizada en agosto de 2026. La diferencia es importante: los archivos quadlet necesitan la versión 4.4 o posterior, y los archivos quadlet de .pod necesitan la versión 5.0. Ejecute podman --version antes de copiar un ejemplo de la documentación del proyecto.
Cada usuario rootless necesita un rango de ID subordinados:
grep "$USER" /etc/subuid /etc/subgidUn usuario creado mediante adduser en Ubuntu obtiene un rango automáticamente. Un usuario creado mediante useradd -M o mediante una herramienta de configuración a menudo no lo obtiene. El error lo indica:
Error: cannot find UID/GID for user deploy: no subuid ranges found for user "deploy" in /etc/subuid - check rootless mode in man pages.Asigne un rango y restablezca después el almacenamiento de ese usuario para aplicar la nueva asignación:
sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 deploy
podman system migrateOtra sorpresa del primer uso: Podman no presupone Docker Hub. Un nombre corto de imagen se resuelve según unqualified-search-registries en /etc/containers/registries.conf. En un script sin una terminal asociada, la extracción falla con short-name resolution enforced but cannot prompt without a TTY. Escriba siempre el nombre completo. Use docker.io/library/nginx:1.27 en lugar de nginx.
Qué ofrecen realmente los contenedores sin root en un servidor alquilado
Un contenedor sin root se ejecuta dentro de un espacio de nombres de usuario, una función del kernel que proporciona a un proceso su propio mapa privado de identificadores de usuario. Dentro del espacio de nombres, el superusuario del contenedor tiene el UID (identificador de usuario) 0. Fuera de él, en el VPS, ese mismo proceso es el usuario normal con el que inicia sesión. El root del contenedor no es el root del host.
Ese es el alcance real de la ventaja. Una imagen que insiste en ejecutarse como root, una aplicación web con una vulnerabilidad de ejecución remota de código o una evasión que depende de tener el UID 0 fuera del contenedor terminan teniendo los permisos de su usuario sin privilegios, no los de la máquina. El modo sin root no le protege de los errores del kernel ni de sus propios archivos, porque el proceso que escapa se ejecuta como usted y puede leer todo lo que usted pueda leer. La unidad aislada importa como mínimo tanto como el mapeo de UID. Esto se ve con más claridad al lado, en una jail de FreeBSD, que encapsula todo un userland que usted administra como una máquina pequeña, en lugar de una imagen por capas descargada de un registro.
Docker también puede ejecutarse sin root. dockerd-rootless-setuptool.sh install configura un daemon por usuario y funciona bien. La diferencia está en hacia dónde apunta la configuración predeterminada. Con Podman obtiene el modo sin root de forma predeterminada, por lo que el primer fallo será un contenedor que no puede asociarse al puerto 80, en lugar de un servicio que se ejecutó silenciosamente como root durante dos años.
¿Por qué los archivos de mi volumen pertenecen al UID 100999?
Se debe al mismo espacio de nombres de usuario. El UID 0 del contenedor se asigna a tu UID del host. El UID 1 del contenedor se asigna al primer ID del intervalo subuid, y los valores aumentan a partir de ahí. Con un intervalo que empieza en 100000, el UID 1000 del contenedor aparece en el host como 100999.
mkdir -p "$PWD/data"
podman run --rm --user 1000 -v "$PWD/data:/data" docker.io/library/alpine:3 sh -c 'id -u; touch /data/f'
ls -ln "$PWD/data"El contenedor muestra 1000. El listado del host muestra como propietario 100999, porque 100000 más 1000 menos 1 es 100999. No hay ningún problema, y un chown normal no lo solucionará, porque tu usuario sin privilegios no puede cambiar la propiedad de los archivos fuera del espacio de nombres.
Hay cuatro soluciones:
podman unshare chown 1000:1000 "$PWD/data"ejecutachowndentro del mismo espacio de nombres de usuario, donde los números tienen el significado que espera el contenedor.-v "$PWD/data:/data:U"pide a Podman que corrija la propiedad del directorio de origen. Úsalo con un directorio nuevo, no con datos que quieras conservar.--userns=keep-idasigna tu UID del host al mismo UID dentro del contenedor, de modo que los archivos nuevos pertenezcan a tu usuario.- Un volumen con nombre, como
-v appdata:/data, evita el problema, porque Podman lo crea dentro de tu propio almacenamiento con la propiedad correcta.
Si ya te has encontrado con este problema en Docker, es el mismo problema, pero una capa más arriba. Las variables PUID y PGID que exponen muchas imágenes establecen el UID que usa el proceso dentro del contenedor y, con Podman rootless, ese UID se asigna después una segunda vez. PUID=1000 dentro de un contenedor rootless sigue escribiendo archivos en el host cuyo propietario es 100999. Elige los valores teniendo en cuenta esa segunda asignación o mueve los datos a un volumen con nombre y deja de preocuparte por ello.
Hay dos observaciones más sobre los montajes. Las opciones :z y :Z que aparecen en los ejemplos de Fedora y RHEL sirven para cambiar las etiquetas de SELinux, y Ubuntu usa AppArmor, por lo que allí no tienen ningún efecto. Además, Podman rootless no puede montar un directorio del host que tu usuario no pueda leer. Ese es precisamente el comportamiento esperado, no un fallo.
¿Por qué rootless Podman se niega a publicar el puerto 80?
Porque enlazar un puerto inferior a 1024 requiere un privilegio que su usuario no tiene. El error indica la solución:
Error: rootlessport cannot expose privileged port 80, you can add 'net.ipv4.ip_unprivileged_port_start=80' to /etc/sysctl.conf (currently 1024), or choose a larger port number (>= 1024): listen tcp 0.0.0.0:80: bind: permission deniedHay dos soluciones válidas. Reduzca el umbral para todo el host:
echo 'net.ipv4.ip_unprivileged_port_start=80' | sudo tee /etc/sysctl.d/99-podman.conf
sudo sysctl --system
sysctl net.ipv4.ip_unprivileged_port_startEl último comando debería devolver 80. Tenga claro qué hace esta configuración: ahora todos los usuarios de la máquina pueden enlazar los puertos 80 y 443, no sólo el usuario que ejecuta los contenedores. En un VPS administrado por una sola persona, este compromiso es aceptable. En un equipo que aloja cuentas de otros usuarios, no lo es. La otra opción es publicar el puerto 8080 y colocar un proxy inverso delante. Ahí es donde quiere emitir y renovar certificados con certbot en nginx de todos modos.
La publicación sin root también cambia lo que ve su aplicación. Podman 4.x usa slirp4netns con el controlador de puertos rootlesskit de forma predeterminada. Las conexiones reenviadas llegan con una dirección de origen reescrita, por lo que el registro de acceso registra a todos los visitantes como 10.0.2.100. Podman 5.0 cambió el valor predeterminado a pasta, que conserva la dirección real del cliente. En 4.x, --network slirp4netns:port_handler=slirp4netns restaura la dirección de origen real, con cierto coste de rendimiento.
Aquí hay una ventaja importante. Un puerto publicado sin root es un socket de escucha normal propiedad de un proceso sin privilegios. Por tanto, las reglas de entrada del firewall se aplican a él. Docker publica puertos escribiendo reglas NAT (network address translation) y sus propias reglas de aceptación para el reenvío. Precisamente por eso un puerto publicado por Docker ignora la regla de ufw que creía que lo bloqueaba. Podman con root usa una configuración similar y tiene la misma limitación. El modo sin root no.
¿Siguen funcionando mis archivos de Docker Compose con Podman?
En general, sí, mediante dos métodos distintos. El primero es podman-compose, una implementación independiente que lee el mismo archivo y utiliza la CLI de Podman:
sudo apt install -y podman-compose
podman-compose up -d
podman psEl segundo consiste en usar Docker Compose real, conectado a la API compatible con Docker de Podman mediante un socket por usuario:
systemctl --user enable --now podman.socket
export DOCKER_HOST="unix:///run/user/$(id -u)/podman/podman.sock"
docker compose up -d
podman psdocker compose ps y podman ps deberían mostrar los mismos contenedores, porque sólo existe un conjunto de contenedores. La resolución de nombres también funciona: el backend de red predeterminado de Podman, netavark, ejecuta aardvark-dns, por lo que los contenedores de una red definida por el usuario se encuentran entre sí por nombre.
Existen limitaciones reales. Todo lo que monte /var/run/docker.sock debe apuntar al socket de Podman o eliminarse. network_mode: host se comporta de forma diferente dentro de un espacio de nombres de usuario. depends_on con condition: service_healthy tiene un nivel de compatibilidad irregular según la versión de podman-compose. restart: always no sobrevive por sí solo a un reinicio, algo que se corrige en la siguiente sección. Compose sigue siendo una buena forma de describir una pila de varios contenedores en un solo archivo, y con Podman actúa como una capa de traducción. Para una pila que se vaya a mantener durante años, conviértala a quadlets y mantenga una sola abstracción en lugar de dos.
Pods: la idea que Docker no resuelve
Un pod es un grupo de contenedores que comparten un espacio de nombres de red. Podman inicia un contenedor infra pequeño para mantener abierto ese espacio de nombres. Después, los miembros se comunican entre sí mediante 127.0.0.1, sin una red definida por el usuario ni mecanismos de descubrimiento de servicios.
podman pod create --name app -p 8080:80
podman run -d --pod app --name app-cache docker.io/library/redis:7
podman run -d --pod app --name app-web docker.io/library/nginx:1.27
podman pod ps
podman ps --podpodman pod ps debe mostrar el pod Running con tres contenedores, incluido el contenedor de infraestructura. Ahora, el contenedor web se conecta a Redis mediante 127.0.0.1:6379 en lugar de app-cache:6379. Del espacio de nombres compartido se derivan dos reglas: publique los puertos en el pod, nunca en un miembro, y ningún par de miembros puede escuchar en el mismo puerto.
Este es el modelo de Kubernetes y Podman lo adopta directamente. podman kube generate app > app.yaml genera un manifiesto de Kubernetes a partir de lo que está en ejecución. En paquetes antiguos aparece como podman generate kube. podman kube play app.yaml lo recrea en otro host. Quadlet tiene un tipo de unidad .kube que ejecuta ese archivo como un servicio de systemd. Es una forma realmente distinta de agrupar servicios y el motivo más importante para elegir Podman si Kubernetes forma parte de sus planes.
Inicio automático sin un daemon: unidades quadlet
Quadlet es un generador de systemd. Convierte un archivo breve que describe un contenedor en un servicio real de systemd durante el arranque. Los archivos se guardan en ~/.config/containers/systemd/ para un usuario rootless o en /etc/containers/systemd/ para root.
~/.config/containers/systemd/caddy.container:
[Unit]
Description=Caddy web server
After=network-online.target
[Container]
Image=docker.io/library/caddy:2
ContainerName=caddy
PublishPort=8080:80
Volume=caddy-data.volume:/data
Environment=TZ=UTC
AutoUpdate=registry
[Service]
Restart=always
MemoryMax=512M
[Install]
WantedBy=default.target~/.config/containers/systemd/caddy-data.volume puede estar casi vacío, porque el encabezado de sección es el que crea el volumen:
[Volume]systemctl --user daemon-reload
systemctl --user start caddy
systemctl --user status caddy
journalctl --user -u caddy -n 50El nombre del servicio procede del nombre del archivo: caddy.container se convierte en caddy.service. No ejecute systemctl --user enable caddy. Las unidades generadas no se pueden habilitar y systemd responde Failed to enable unit: Unit /run/user/1000/systemd/generator/caddy.service is transient or generated.. La sección [Install] es la que inicia el contenedor durante el arranque, y daemon-reload regenera la unidad después de editar el archivo.
Ahora, la configuración que suele causar problemas:
sudo loginctl enable-linger deploy
loginctl show-user deploy --property=LingerDebe aparecer Linger=yes. Sin linger, systemd elimina toda la sesión del usuario cuando se cierra la última conexión SSH. Como consecuencia, todos los contenedores rootless se detienen y ninguno vuelve a iniciarse durante el arranque. Si los contenedores desaparecen al cerrar la sesión, esta es siempre la causa.
Como el contenedor es el proceso principal de una unidad de servicio normal, los controles propios de systemd se aplican directamente. MemoryMax= y CPUQuota= en la sección [Service] funcionan exactamente igual que en cualquier otro servicio que limite con systemd. Esto requiere cgroup v2 (versión 2 del grupo de control), que Ubuntu usa de forma predeterminada desde 22.04. Confírmelo con podman info | grep -i cgroup.
Las actualizaciones tienen un mecanismo equivalente. AutoUpdate=registry junto con systemctl --user enable --now podman-auto-update.timer comprueba si hay una imagen más reciente en el registro con la misma etiqueta, reinicia la unidad y vuelve a la imagen anterior si el nuevo contenedor no se inicia. Ejecute primero podman auto-update --dry-run para ver qué cambios realizaría. El comando anterior podman generate systemd todavía existe y está obsoleto, por lo que debe escribir quadlets para todo lo nuevo.
Dónde funciona el alias de docker y dónde no
sudo apt install -y podman-docker
sudo touch /etc/containers/nodocker
docker pspodman-docker instala un contenedor de /usr/bin/docker que llama a Podman. Sin el archivo nodocker, cada llamada muestra primero Emulate Docker CLI using podman. Create /etc/containers/nodocker to quiet msg.. El contenedor cubre los comandos que usa a diario: run, ps, logs, exec, build, pull, push, inspect, cp, volume y network.
Lo que no se conserva es una lista más corta y más importante. Swarm mode no tiene equivalente, por lo que una pila de Swarm no tiene dónde ejecutarse. Las herramientas que se comunican con el socket de Docker necesitan que se exporte el socket de Podman, y algunas siguen detectando la diferencia; el proveedor de Docker de Traefik funciona cuando se configura para usar /run/user/<uid>/podman/podman.sock, mientras que Watchtower no tiene equivalente, porque podman auto-update realiza esa tarea. El almacenamiento es independiente, por lo que Podman no puede ver las imágenes que ya descargó con Docker, y podman images en un host de Docker con actividad empieza vacío.
Migración paso a paso de una pila en ejecución
- Cree o seleccione el usuario sin privilegios que será propietario de los contenedores y confirme que tiene un rango en
/etc/subuid. - Vuelva a descargar todo lo que proceda de un registro, usando nombres completos. Podman tiene su propio almacén de imágenes y no leerá el de Docker.
- Transfiera las imágenes compiladas localmente con
docker save app:1.4 | podman load. - Detenga el contenedor de Docker, copie el contenido de cada volumen fuera de
/var/lib/docker/volumes/<name>/_datay corrija después el propietario conpodman unshare chown -R 1000:1000 <path>. - Resuelva el uso de puertos: publique un puerto superior a 1024 detrás de un proxy inverso o configure
net.ipv4.ip_unprivileged_port_start. - Escriba un archivo quadlet por contenedor, ejecute
systemctl --user daemon-reloade inicie cada servicio. - Ejecute
sudo loginctl enable-linger <user>, reinicie el VPS, vuelva a iniciar sesión y compruebe quepodman psvuelve a mostrar todos los servicios.
Los dos motores no comparten nada: tienen almacenamiento de imágenes y redes independientes. Por tanto, puede ejecutar ambos durante la migración. Lo único por lo que pueden competir es un número de puerto del host. Migre un servicio, supervíselo durante un día y después migre el siguiente.
Podman frente a Docker: ¿cuál debe usar en su VPS?
Mantenga Docker si su stack se basa en archivos de Compose que también mantienen otras personas, o si depende de herramientas que se comunican con el socket de Docker. La compatibilidad con lo que escribe el resto del equipo es una ventaja real, y Docker ofrece más compatibilidad. Un equipo cuyos portátiles ejecutan Docker también obtiene una ventaja concreta al usar el mismo motor en producción.
Cambie a Podman si el VPS ejecuta un conjunto reducido de servicios que controla de extremo a extremo, o si quiere que cada aplicación se ejecute con su propio usuario sin privilegios y sin ningún grupo docker en el sistema. La integración con la distribución también cuenta: RHEL y sus reconstrucciones distribuyen Podman como motor compatible, por lo que en esos sistemas Podman suele causar menos problemas. Si aun así quiere usar Docker en uno de esos hosts, la ruta de dnf en Rocky Linux y AlmaLinux empieza por eliminar el wrapper podman-docker que ya controla el comando docker. Si ya supervisa todo lo demás mediante unidades de systemd, los quadlets le parecerán una pieza que faltaba, no una herramienta nueva que deba aprender.
Conviene mencionar una opción intermedia. Podman rootful se comporta de forma muy similar a Docker, conserva el comando docker mediante el wrapper y elimina el daemon que debe estar siempre en ejecución. También renuncia a la parte rootless, que es la que cambia su posición de seguridad, por lo que debe considerarlo un paso intermedio.
Si todavía está creando su primer host de contenedores, la ruta de configuración y refuerzo de seguridad de Docker en un VPS nuevo es más corta, y todo ese conocimiento seguirá siendo útil. Las imágenes y los volúmenes son los mismos objetos en ambos motores, por lo que una migración cambia la forma de supervisar los servicios y muy poco más.
FAQ
¿Podman sustituye directamente a Docker?
Para los comandos que escribe, casi. La instalación de podman-docker proporciona un envoltorio /usr/bin/docker, y run, ps, build, logs y exec funcionan de la misma forma. No sustituye al daemon. Swarm no tiene un equivalente, las herramientas que se conectan a /var/run/docker.sock deben apuntar al socket de Podman por usuario y las imágenes descargadas por Docker siguen siendo invisibles para Podman porque ambos mantienen almacenamientos independientes.
¿Por qué se detienen mis contenedores rootless de Podman cuando cierro la sesión SSH?
Porque systemd detiene la sesión de usuario y todos sus servicios cuando se cierra el último inicio de sesión. Ejecute sudo loginctl enable-linger <user> y compruebe que loginctl show-user <user> --property=Linger muestra Linger=yes. Linger mantiene en ejecución la instancia de systemd de ese usuario sin una sesión activa. Esto también permite que los contenedores vuelvan a iniciarse después de un reinicio.
¿Por qué los archivos de mi volumen pertenecen a UID 100999?
Podman rootless asigna el UID de contenedor 0 al usuario del host. Después asigna el UID de contenedor 1 y los siguientes al rango subuid del usuario. Con un rango que empieza en 100000, el UID de contenedor 1000 se convierte en 100999 en el host. Corríjalo desde dentro del namespace con podman unshare chown 1000:1000 /path/to/data, monte el volumen con la opción :U durante la primera ejecución o use --userns=keep-id para que los UID del contenedor coincidan con los suyos.
¿Puedo seguir usando docker-compose.yml con Podman?
Sí, de dos formas. podman-compose lee el archivo y controla directamente la CLI de Podman. También puede habilitar el socket de compatibilidad con systemctl --user enable --now podman.socket, establecer DOCKER_HOST=unix:///run/user/$(id -u)/podman/podman.sock y ejecutar un docker compose real contra ese socket. Espere limitaciones con network_mode: host, con los servicios que montan el socket de Docker y con restart: always, que necesita una unidad quadlet y linger para sobrevivir a un reinicio.
¿Rootless hace realmente que los contenedores sean más seguros?
Elimina un riesgo concreto: un proceso que escape de un contenedor rootless conserva los permisos del usuario sin privilegios en lugar de los de root. Esto es útil y explica por qué el grupo equivalente a root, docker, no tiene un equivalente en Podman rootless. No evita las vulnerabilidades del kernel ni protege los archivos que puede leer el propio usuario. Por tanto, mantenga el resto de medidas de endurecimiento que aplicaría en cualquier servidor.