Podman ou Docker sur un VPS : quelles différences ?
Podman est rootless et sans daemon. Découvrez les effets sur Compose, les quadlets, les ports et les droits des volumes sur un serveur VPS loué.
Ce qui différencie réellement Podman et Docker
Podman et Docker exécutent les mêmes images OCI (Open Container Initiative) sur un VPS. Le choix ne dépend donc pas des logiciels que vous pouvez exécuter. La différence concerne le modèle de processus. Docker exécute un daemon root qui contrôle tous les conteneurs. La commande docker est un petit client qui demande à ce daemon d’effectuer le travail. Podman n’a pas de daemon : podman run démarre le conteneur comme processus enfant du processus qui l’a appelé, avec votre propre compte non privilégié.
Tout le reste découle de ce fait. Le démarrage automatique relève de systemd et non du daemon. La propriété des volumes passe par un user namespace. Le propriétaire que vous voyez avec ls -l sur l’hôte n’est donc pas celui que voit le conteneur. Les ports inférieurs à 1024 refusent l’écoute tant que vous n’avez pas modifié un paramètre du noyau. La CLI (interface en ligne de commande) docker continue de fonctionner avec un wrapper, jusqu’à ce qu’une opération nécessite le socket Docker.
Aucun daemon : ce qui s’exécute réellement au démarrage d’un conteneur
Sur un hôte Docker, pstree -a affiche dockerd en tant que root, containerd à côté, et un containerd-shim-runc-v2 par conteneur en cours d’exécution. Votre application est un processus enfant de ce shim, et le shim est un processus enfant du PID 1. Rien ne relie le conteneur au shell qui l’a démarré. Arrêtez le daemon et vous perdez le plan de contrôle de tous les conteneurs de l’hôte. Avec le paramètre live-restore par défaut désactivé, systemctl restart docker redémarre également vos conteneurs.
Podman n’a pas de processus équivalent. Démarrez un conteneur et vous obtenez un processus conmon (moniteur de conteneur) qui détient le processus principal du conteneur. Ce processus appartient à l’utilisateur qui a exécuté la commande.
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 doit afficher conmon exécuté avec votre utilisateur de connexion, et non avec root. curl doit afficher 200. Aucun service central ne possédant le conteneur, sudo apt upgrade podman n’arrête rien de ce qui est déjà en cours d’exécution. Le crash du moniteur d’un conteneur ne peut pas entraîner l’arrêt des autres.
L’absence de daemon a aussi un coût. Rien ne démarre vos conteneurs après un redémarrage. Le --restart=always de Docker est une garantie que le daemon applique au démarrage. Podman le remplace par systemd, qui est utilisé dans la section consacrée à quadlet ci-dessous.
Le socket constitue l’autre moitié du problème. /var/run/docker.sock est un endpoint d’API (interface de programmation d’application) appartenant à root. Tout processus pouvant y écrire peut démarrer un conteneur privilégié qui monte le système de fichiers de l’hôte. Ajouter un utilisateur au groupe docker lui accorde les privilèges root par un moyen détourné, plus lent. Il est utile de lire ce point avec accorder à chaque compte de service uniquement les accès dont il a besoin. Podman n’expose aucun socket sans demande explicite. Le socket obtenu appartient à un seul utilisateur, à /run/user/<uid>/podman/podman.sock.
Installer Podman sur Ubuntu 24.04 et vérifier que le mode rootless fonctionne réellement
sudo apt update
sudo apt install -y podman uidmap
podman --version
podman info | grep -i rootlessLe paquet uidmap fournit newuidmap et newgidmap. Ce sont les assistants setuid qui permettent à un utilisateur ordinaire de s’attribuer une plage d’identifiants subordonnés. Sans eux, les conteneurs rootless ne démarrent pas. podman info doit afficher rootless: true.
Ubuntu 24.04 fournit Podman 4.9 et Debian 13 fournit Podman 5.x, vérifié en août 2026. Cette différence est importante : les fichiers quadlet nécessitent la version 4.4 ou ultérieure, tandis que les fichiers quadlet .pod nécessitent la version 5.0. Exécutez podman --version avant de copier un exemple de la documentation upstream.
Chaque utilisateur rootless a besoin d’une plage d’identifiants subordonnés :
grep "$USER" /etc/subuid /etc/subgidUn utilisateur créé avec adduser sur Ubuntu reçoit automatiquement une plage. Un utilisateur créé avec useradd -M ou par un outil de configuration n’en reçoit souvent pas. Le message d’erreur l’indique :
Error: cannot find UID/GID for user deploy: no subuid ranges found for user "deploy" in /etc/subuid - check rootless mode in man pages.Attribuez une plage, puis réinitialisez le stockage de cet utilisateur pour utiliser le nouveau mapping :
sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 deploy
podman system migrateAutre surprise lors de la première exécution : Podman ne suppose pas l’utilisation de Docker Hub. Un nom d’image court est résolu à partir de unqualified-search-registries dans /etc/containers/registries.conf. Dans un script sans terminal associé, le pull échoue avec short-name resolution enforced but cannot prompt without a TTY. Écrivez toujours le nom complet. Utilisez docker.io/library/nginx:1.27 plutôt que nginx.
Ce que les conteneurs rootless vous apportent réellement sur un serveur loué
Un conteneur rootless s’exécute dans un user namespace, une fonctionnalité du noyau qui attribue à un processus sa propre table privée d’identifiants utilisateur. Dans le namespace, le superutilisateur du conteneur est l’UID (user ID) 0. À l’extérieur, sur votre VPS, ce même processus s’exécute avec les droits de votre utilisateur de connexion standard. Le compte root du conteneur n’est pas le compte root de l’hôte.
C’est l’ampleur réelle du gain. Une image qui exige de s’exécuter en tant que root, une application web présentant une faille d’exécution de code à distance, ou un escape qui dépend de l’UID 0 à l’extérieur du conteneur : dans tous ces cas, le processus obtient les permissions de votre utilisateur sans privilèges, et non celles de la machine. Le mode rootless ne vous protège pas contre les bugs du noyau. Il ne protège pas non plus vos propres fichiers, car le processus qui s’échappe s’exécute avec vos droits et peut lire tout ce que vous pouvez lire. L’isolation de l’unité compte au moins autant que le mapping des UID. C’est plus facile à voir juste à côté, dans un jail FreeBSD qui encapsule tout un userland que vous administrez comme une petite machine, plutôt que dans une image en couches récupérée depuis un registry.
Docker peut également fonctionner en mode rootless. dockerd-rootless-setuptool.sh install configure un daemon par utilisateur et cela fonctionne bien. La différence tient à la direction prise par défaut. Avec Podman, le mode rootless est appliqué sans intervention de votre part. Votre premier problème est donc un conteneur qui ne peut pas écouter sur le port 80, au lieu d’un service qui s’est exécuté discrètement en tant que root pendant deux ans.
Pourquoi mes fichiers de volume appartiennent-ils à l’UID 100999 ?
Cela vient du même user namespace. L’UID 0 du conteneur est associé à votre UID sur l’hôte. L’UID 1 du conteneur est associé au premier identifiant de votre plage subuid, puis les identifiants s’incrémentent. Avec une plage qui commence à 100000, l’UID 1000 du conteneur correspond à l’UID 100999 sur l’hôte.
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"Le conteneur affiche 1000. La liste de fichiers sur l’hôte affiche le propriétaire 100999, car 100000 plus 1000 moins 1 donne 100999. Rien n’est cassé. Un simple chown ne corrigera pas le problème, car votre utilisateur non privilégié ne peut pas modifier le propriétaire des fichiers en dehors du namespace.
Quatre solutions sont possibles :
podman unshare chown 1000:1000 "$PWD/data"exécute le chown dans le même user namespace, où les identifiants ont le même sens que dans le conteneur.-v "$PWD/data:/data:U"demande à Podman de corriger le propriétaire du répertoire source. Utilisez cette option avec un répertoire vide, et non avec des données importantes.--userns=keep-idassocie votre UID sur l’hôte au même UID dans le conteneur. Les nouveaux fichiers vous appartiennent ainsi directement.- Un volume nommé tel que
-v appdata:/dataévite le problème, car Podman le crée dans votre propre stockage avec le propriétaire approprié.
Si vous avez rencontré ce problème avec Docker, il s’agit du même problème, à un niveau supplémentaire. Les variables PUID et PGID exposées par de nombreuses images définissent l’UID utilisé par le processus dans le conteneur. Avec Podman rootless, cet UID est ensuite associé une seconde fois. PUID=1000 dans un conteneur rootless écrit toujours des fichiers sur l’hôte avec l’UID 100999. Tenez compte de cette seconde association lorsque vous choisissez les identifiants, ou déplacez les données dans un volume nommé pour ne plus avoir à vous en préoccuper.
Deux autres points concernent les mounts. Les options :z et :Z que vous voyez dans les exemples Fedora et RHEL servent au relabeling SELinux. Ubuntu utilise AppArmor, donc ces options n’y ont aucun effet. Podman rootless ne peut pas non plus monter un répertoire de l’hôte que votre utilisateur ne peut pas lire. C’est le comportement attendu, pas un défaut.
Pourquoi Podman rootless refuse-t-il de publier le port 80 ?
Parce que l’écoute sur un port inférieur à 1024 nécessite un privilège que votre utilisateur ne possède pas. Le message d’erreur indique la correction :
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 deniedDeux solutions sont possibles. Abaissez le seuil pour tout l’hôte :
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_startLa dernière commande doit renvoyer 80. Soyez clair sur l’effet de ce réglage : tous les utilisateurs de la machine peuvent désormais écouter sur 80 et 443, et pas uniquement celui qui exécute les conteneurs. Sur un VPS administré par une seule personne, ce compromis est acceptable. Sur une machine qui héberge les comptes d’autres utilisateurs, il ne l’est pas. L’autre solution consiste à publier sur 8080 et à placer un reverse proxy devant, ce qui correspond de toute façon à l’architecture souhaitée pour les certificats délivrés et renouvelés par certbot sur nginx.
La publication rootless modifie également ce que voit votre application. Podman 4.x utilise slirp4netns avec le gestionnaire de ports rootlesskit par défaut. Les connexions transférées arrivent avec une adresse source réécrite. Le journal d’accès enregistre donc chaque visiteur comme 10.0.2.100. Podman 5.0 a remplacé cette valeur par défaut par pasta, qui conserve l’adresse réelle du client. Sur 4.x, --network slirp4netns:port_handler=slirp4netns rétablit l’adresse source réelle, au prix d’une baisse du débit.
Il y a toutefois un point positif. Un port publié en rootless est un socket en écoute ordinaire, détenu par un processus normal. Les règles d’entrée de votre firewall s’y appliquent donc. Docker publie les ports en écrivant des règles NAT (network address translation) ainsi que ses propres règles d’acceptation du forwarding. C’est précisément pourquoi un port Docker publié ignore la règle ufw qui devait le bloquer. Podman rootful utilise une configuration similaire et présente le même piège. Le mode rootless ne le présente pas.
Mes fichiers Docker Compose fonctionnent-ils toujours avec Podman ?
Dans la plupart des cas, oui, par deux moyens différents. Le premier est podman-compose, une implémentation distincte qui lit le même fichier et utilise la CLI Podman :
sudo apt install -y podman-compose
podman-compose up -d
podman psLe second consiste à utiliser Docker Compose, qui communique avec l’API compatible Docker de Podman via un socket par utilisateur :
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 et podman ps doivent afficher les mêmes conteneurs, car il n’existe qu’un seul ensemble de conteneurs. La résolution des noms fonctionne également : le backend réseau par défaut de Podman, netavark, exécute aardvark-dns. Les conteneurs d’un réseau défini par l’utilisateur peuvent donc se trouver par leur nom.
Certaines limites sont réelles. Tout ce qui monte /var/run/docker.sock doit être configuré pour utiliser le socket Podman, ou supprimé. network_mode: host se comporte différemment dans un user namespace. La prise en charge de depends_on avec condition: service_healthy varie selon les versions de podman-compose. restart: always ne survit pas seul à un redémarrage, ce que la section suivante corrige. Compose reste un bon moyen de décrire une stack composée de plusieurs conteneurs dans un seul fichier. Sous Podman, il sert de couche de traduction. Pour une stack que vous comptez conserver pendant plusieurs années, convertissez-la en quadlets et utilisez une seule abstraction au lieu de deux.
Pods : le concept auquel Docker n’apporte pas de réponse
Un pod est un groupe de conteneurs qui partagent un même espace de noms réseau. Podman démarre un petit conteneur infra pour maintenir cet espace de noms ouvert. Les membres peuvent ensuite communiquer entre eux sur 127.0.0.1, sans réseau défini par l’utilisateur ni découverte de services.
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 doit afficher le pod Running avec trois conteneurs, en comptant le conteneur d’infrastructure. Le conteneur web peut maintenant joindre Redis à l’adresse 127.0.0.1:6379 au lieu de app-cache:6379. Deux règles découlent de cet espace de noms partagé : publiez les ports sur le pod, jamais sur un membre, et aucun membre ne doit écouter sur le même port qu’un autre.
C’est le modèle de Kubernetes, et Podman l’adopte pleinement. podman kube generate app > app.yaml génère un manifeste Kubernetes à partir de ce qui est en cours d’exécution. Dans les versions plus anciennes, la commande s’écrit podman generate kube. podman kube play app.yaml recrée ensuite cet environnement sur un autre hôte. Quadlet fournit un type d’unité .kube qui exécute un tel fichier comme service systemd. Il s’agit d’une autre façon de regrouper les services, et c’est la raison principale de choisir Podman si Kubernetes fait partie de vos projets.
Démarrage automatique sans daemon : unités quadlet
Quadlet est un générateur systemd. Il transforme un fichier court décrivant un conteneur en véritable service systemd au démarrage. Les fichiers se trouvent dans ~/.config/containers/systemd/ pour un utilisateur rootless, ou dans /etc/containers/systemd/ pour 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 peut être presque vide, car l’en-tête de section suffit à créer le volume :
[Volume]systemctl --user daemon-reload
systemctl --user start caddy
systemctl --user status caddy
journalctl --user -u caddy -n 50Le nom du service provient du nom de fichier : caddy.container devient caddy.service. N’exécutez pas systemctl --user enable caddy. Les unités générées ne peuvent pas être activées, et systemd répond Failed to enable unit: Unit /run/user/1000/systemd/generator/caddy.service is transient or generated.. La section [Install] démarre le conteneur au boot, tandis que daemon-reload régénère l’unité après la modification du fichier.
Voici maintenant le paramètre qui pose problème dans presque tous les cas :
sudo loginctl enable-linger deploy
loginctl show-user deploy --property=LingerAttendez-vous à Linger=yes. Sans linger, systemd détruit toute la session utilisateur lorsque votre dernière connexion SSH se ferme. Tous les conteneurs rootless s’arrêtent alors avec elle et aucun ne redémarre au boot. Si les conteneurs disparaissent lorsque vous vous déconnectez, c’est toujours la cause.
Le conteneur étant le processus principal d’une unité de service ordinaire, les contrôles de systemd s’appliquent directement. MemoryMax= et CPUQuota= dans la section [Service] se comportent exactement comme pour tout autre service que vous contrôlez avec systemd. Cela nécessite cgroup v2 (version 2 des groupes de contrôle), qu’Ubuntu utilise par défaut depuis 22.04. Vérifiez avec podman info | grep -i cgroup.
Les mises à jour disposent d’un mécanisme associé. AutoUpdate=registry et systemctl --user enable --now podman-auto-update.timer vérifient si le registre contient une image plus récente avec le même tag, redémarrent l’unité et reviennent à l’image précédente si le nouveau conteneur ne démarre pas. Exécutez d’abord podman auto-update --dry-run pour voir les changements prévus. L’ancienne commande podman generate systemd existe toujours, mais elle est obsolète : utilisez donc des quadlets pour tout nouveau déploiement.
Là où l’alias docker fonctionne, et là où il ne fonctionne pas
sudo apt install -y podman-docker
sudo touch /etc/containers/nodocker
docker pspodman-docker installe un wrapper /usr/bin/docker qui appelle Podman. Sans le fichier nodocker, chaque appel affiche d’abord Emulate Docker CLI using podman. Create /etc/containers/nodocker to quiet msg.. Le wrapper couvre les commandes que vous utilisez quotidiennement : run, ps, logs, exec, build, pull, push, inspect, cp, volume, network.
Ce qui ne fonctionne pas est moins étendu, mais plus clairement limité. Le mode Swarm n’a pas d’équivalent : une stack Swarm ne peut donc pas être exécutée. Les outils qui communiquent avec le socket Docker nécessitent l’exportation du socket Podman, et certains détectent malgré tout la différence ; le provider Docker de Traefik fonctionne lorsqu’il pointe vers /run/user/<uid>/podman/podman.sock, tandis que Watchtower n’a aucune utilité, car podman auto-update se charge de cette fonction. Le stockage est séparé. Podman ne peut donc pas voir les images que vous avez déjà téléchargées avec Docker, et podman images est vide au départ sur un hôte Docker déjà chargé.
Migration d’une stack en fonctionnement, étape par étape
- Créez ou choisissez l’utilisateur non privilégié qui possédera les conteneurs, puis vérifiez qu’il dispose d’une plage dans
/etc/subuid. - Téléchargez de nouveau tout ce qui provient d’un registry, en utilisant des noms entièrement qualifiés. Podman possède son propre stockage d’images et ne lit pas celui de Docker.
- Transférez les images construites localement avec
docker save app:1.4 | podman load. - Arrêtez le conteneur Docker, copiez le contenu de chaque volume depuis
/var/lib/docker/volumes/<name>/_data, puis corrigez le propriétaire avecpodman unshare chown -R 1000:1000 <path>. - Réglez la question des ports : publiez un port supérieur à 1024 derrière un reverse proxy, ou configurez
net.ipv4.ip_unprivileged_port_start. - Écrivez un fichier quadlet par conteneur, exécutez
systemctl --user daemon-reload, puis démarrez chaque service. - Exécutez
sudo loginctl enable-linger <user>, redémarrez le VPS, reconnectez-vous et vérifiez quepodman psrépertorie de nouveau tous les services.
Les deux moteurs ne partagent rien : le stockage des images et les réseaux sont distincts. Vous pouvez donc exécuter les deux pendant la migration. Le seul élément sur lequel ils peuvent entrer en conflit est le numéro de port de l’hôte. Migrez un service, surveillez-le pendant une journée, puis passez au suivant.
Podman ou Docker : lequel utiliser sur votre VPS ?
Restez sur Docker si votre stack repose sur des fichiers Compose que d’autres personnes maintiennent également, ou si vous dépendez d’outils qui communiquent avec le socket Docker. La compatibilité avec les configurations utilisées par la plupart des équipes est un avantage réel, et Docker en offre davantage. Une équipe dont tous les postes utilisent Docker bénéficie aussi concrètement du même moteur en production.
Passez à Podman si le VPS héberge quelques services que vous contrôlez de bout en bout, ou si vous voulez exécuter chaque application avec son propre utilisateur non privilégié, sans aucun groupe docker sur le serveur. L’intégration avec la distribution compte également : RHEL et ses reconstructions fournissent Podman comme moteur pris en charge. Sur ces systèmes, Podman est donc la solution qui réserve le moins de surprises. Si vous voulez malgré tout utiliser Docker sur l’un de ces hôtes, la méthode avec dnf sur Rocky Linux et AlmaLinux commence par supprimer l’encapsuleur podman-docker qui possède déjà la commande docker sur ces systèmes. Si vous supervisez déjà tout le reste avec des unités systemd, les quadlets donneront l’impression de compléter naturellement l’existant plutôt que d’ajouter un nouvel outil à apprendre.
Une solution intermédiaire mérite d’être mentionnée. Podman exécuté avec root se comporte largement comme Docker, conserve la commande docker grâce à l’encapsuleur et supprime malgré tout le daemon qui devrait rester actif en permanence. Il abandonne toutefois la partie rootless, qui est précisément celle qui modifie votre niveau de sécurité. Considérez donc cette configuration comme une étape intermédiaire.
Si vous construisez encore votre premier hôte de conteneurs, la procédure d’installation et de sécurisation de Docker sur un VPS neuf est le chemin le plus court, et ces connaissances resteront utiles. Les images et les volumes sont les mêmes objets avec les deux moteurs. Un changement modifie donc surtout la manière dont vos services sont supervisés, et très peu le reste.
FAQ
Podman est-il un remplacement direct de Docker ?
Pour les commandes que vous saisissez, c’est presque le cas. L’installation de podman-docker fournit un wrapper /usr/bin/docker, et run, ps, build, logs et exec se comportent de la même manière. Ce n’est pas un remplacement du daemon. Swarm n’a pas d’équivalent, les outils qui se connectent à /var/run/docker.sock doivent être configurés pour utiliser le socket Podman de l’utilisateur, et les images téléchargées par Docker restent invisibles pour Podman, car les deux logiciels utilisent des stockages distincts.
Pourquoi mes conteneurs Podman rootless s’arrêtent-ils quand je me déconnecte de SSH ?
Parce que systemd arrête la session utilisateur, ainsi que tous les services utilisateur, lorsque votre dernière session se ferme. Exécutez sudo loginctl enable-linger <user>, puis vérifiez que loginctl show-user <user> --property=Linger affiche Linger=yes. Le mode linger maintient l’instance systemd de cet utilisateur en fonctionnement lorsqu’aucune session n’est active. C’est aussi ce qui permet aux conteneurs de redémarrer après un reboot.
Pourquoi les fichiers de mon volume appartiennent-ils à l’UID 100999 ?
Podman rootless mappe l’UID 0 du conteneur vers votre utilisateur sur l’hôte, puis mappe l’UID 1 du conteneur et les suivants vers votre plage de subuid. Avec une plage qui commence à 100000, l’UID 1000 du conteneur devient 100999 sur l’hôte. Corrigez cela depuis l’intérieur du namespace avec podman unshare chown 1000:1000 /path/to/data, montez le volume avec le flag :U lors du premier lancement, ou utilisez --userns=keep-id pour que les UID des conteneurs correspondent aux vôtres.
Puis-je continuer à utiliser docker-compose.yml avec Podman ?
Oui, de deux manières. podman-compose lit le fichier et pilote directement la CLI Podman. Vous pouvez aussi activer le socket de compatibilité avec systemctl --user enable --now podman.socket, définir DOCKER_HOST=unix:///run/user/$(id -u)/podman/podman.sock, puis exécuter le véritable docker compose dessus. Des difficultés sont à prévoir avec network_mode: host, avec les services qui montent le socket Docker, et avec restart: always, qui nécessite une unité quadlet et le mode linger pour survivre à un reboot.
Le mode rootless rend-il réellement les conteneurs plus sécurisés ?
Il supprime un risque précis : un processus qui s’échappe d’un conteneur rootless conserve les permissions de votre utilisateur non privilégié, et non celles de root. C’est utile, et c’est pourquoi le groupe docker, équivalent à root, n’a pas d’équivalent avec Podman rootless. Cela n’empêche pas les vulnérabilités du kernel et ne protège pas les fichiers que votre propre utilisateur peut lire. Conservez donc les autres mesures de hardening que vous appliqueriez sur n’importe quel serveur.