Podman ou Docker sur un VPS : quelles différences ?
Podman est rootless et sans daemon. Découvrez les effets concrets sur un VPS loué : fichiers Compose, quadlets, ports et droits des volumes.
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 tient au modèle de processus. Docker exécute un daemon avec les privilèges root qui gère tous les conteneurs. La commande docker est un client léger 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 utilisateur non privilégié.
Tout le reste découle de ce fait. Le démarrage automatique devient la responsabilité de systemd, et non plus celle du daemon. La propriété des volumes transite 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 la liaison tant que vous n’avez pas modifié un paramètre du kernel. La CLI (interface de ligne de commande) docker continue de fonctionner grâce à 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, lui-même enfant du PID 1. Rien ne relie le conteneur au shell qui l’a démarré. Arrêtez le daemon et vous perdez le control plane de tous les conteneurs de la machine. Si le paramètre live-restore est 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 seul processus conmon (container monitor) qui héberge le processus principal du conteneur. Il 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 monitor d’un conteneur ne peut pas entraîner l’arrêt des autres.
L’absence de daemon a toutefois un coût. Rien ne démarre vos conteneurs après un redémarrage. Le --restart=always de Docker est une garantie assurée par le daemon au boot. Podman le remplace par systemd. C’est l’objectif de la section suivante sur les quadlets.
Le socket est l’autre moitié du problème. /var/run/docker.sock est un endpoint d’API (application programming interface) appartenant à root. Tout processus capable d’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 chemin indirect, ce qui mérite d’être lu avec accorder à chaque compte de service uniquement les accès dont il a besoin. Podman n’expose aucun socket par défaut. Lorsque vous en demandez un, il appartient à un seul utilisateur sur /run/user/<uid>/podman/podman.sock.
Installer Podman sur Ubuntu 24.04 et vérifier que le mode rootless est réellement actif
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 utilitaires setuid qui permettent à un utilisateur ordinaire de s’attribuer une plage d’identifiants secondaires. 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, selon les vérifications effectuées en août 2026. Cet écart est important, car les fichiers quadlet nécessitent la version 4.4 ou une version ultérieure, tandis que les fichiers quadlet .pod nécessitent la version 5.0. Exécutez podman --version avant de copier un exemple depuis la documentation upstream.
Chaque utilisateur rootless a besoin d’une plage d’identifiants secondaires :
grep "$USER" /etc/subuid /etc/subgidUn utilisateur créé par adduser sur Ubuntu reçoit automatiquement une plage. Un utilisateur créé par 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 appliquer le nouveau mapping :
sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 deploy
podman system migrateAutre surprise lors de la première utilisation : Podman ne suppose pas que les images viennent 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 correspondance privée des identifiants utilisateur. Dans le namespace, le superuser du conteneur est l’UID (user ID) 0. En dehors de celui-ci, sur votre VPS, ce même processus correspond à votre utilisateur de connexion ordinaire. Le root du conteneur n’est pas le 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 vulnérabilité d’exécution de code à distance ou une évasion qui dépend de l’UID 0 en dehors du conteneur : dans tous ces cas, le processus obtient les permissions de votre utilisateur non privilégié, et non celles de la machine. Le mode rootless ne vous protège pas contre les vulnérabilités du noyau. Il ne protège pas non plus vos propres fichiers, car le processus échappé s’exécute avec votre identité et peut lire tout ce que vous pouvez lire.
Docker peut également fonctionner en mode rootless. dockerd-rootless-setuptool.sh install configure un daemon par utilisateur et fonctionne correctement. La différence concerne le comportement par défaut. Avec Podman, le mode rootless est activé sans action supplémentaire. Votre premier problème est donc un conteneur qui ne peut pas écouter sur le port 80, plutôt qu’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 ?
C’est dû au même user namespace. L’UID 0 du conteneur correspond à votre UID sur l’hôte. L’UID 1 du conteneur correspond au premier identifiant de votre plage subuid, puis les identifiants sont attribués dans l’ordre. 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 des fichiers sur l’hôte affiche le propriétaire 100999, car 100000 plus 1000 moins 1 donne 100999. Rien n’est défectueux. Un simple chown ne corrigera pas le problème, car votre utilisateur non privilégié ne peut pas modifier la propriété des fichiers en dehors du namespace.
Quatre solutions sont possibles :
podman unshare chown 1000:1000 "$PWD/data"exécutechowndans le même user namespace. Les identifiants ont alors la même signification que dans le conteneur.-v "$PWD/data:/data:U"demande à Podman de corriger la propriété du répertoire source. Utilisez cette option sur un répertoire neuf, pas sur 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 alors sur l’hôte.- Un volume nommé tel que
-v appdata:/dataévite le problème, car Podman le crée dans votre propre espace de stockage avec la propriété correcte.
Si vous avez déjà rencontré ce problème avec Docker, c’est le même problème, mais un niveau plus haut. Les variables PUID et PGID proposé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 deuxième fois. PUID=1000 dans un conteneur rootless crée donc toujours des fichiers appartenant à l’UID 100999 sur l’hôte. Choisissez les identifiants en tenant compte de cette deuxième association, ou déplacez les données dans un volume nommé pour ne plus avoir à vous en préoccuper.
Deux autres points concernent les montages. Les options :z et :Z présentes dans les exemples Fedora et RHEL servent à réétiqueter les fichiers pour 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 ?
La liaison à 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 l’ensemble de 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 précis sur l’effet de ce réglage : tous les utilisateurs de la machine peuvent désormais lier les ports 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 le port 8080 et à placer un reverse proxy devant, ce qui correspond de toute façon à l’architecture souhaitée pour les certificats émis 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 sous l’adresse 10.0.2.100. Podman 5.0 a remplacé cette valeur par défaut par pasta, qui conserve l’adresse réelle du client. Avec la version 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 pare-feu s’y appliquent donc. Docker publie les ports en ajoutant 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é contourne la règle ufw qui devait le bloquer. Podman rootful utilise une plomberie similaire et présente le même piège. Le mode rootless ne le fait pas.
Les fichiers Docker Compose fonctionnent-ils toujours avec Podman ?
Dans la plupart des cas, oui, par deux méthodes différentes. La première est podman-compose, une implémentation distincte qui lit le même fichier et pilote la CLI Podman :
sudo apt install -y podman-compose
podman-compose up -d
podman psLa seconde consiste à utiliser Docker Compose pour communiquer 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, si bien que les conteneurs d’un réseau défini par l’utilisateur se trouvent entre eux par leur nom.
Les 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 de plusieurs conteneurs dans un seul fichier, et joue le rôle de couche de traduction avec Podman. Pour une stack que vous prévoyez de conserver pendant plusieurs années, convertissez-la en quadlets et gérez 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 namespace réseau. Podman démarre un petit conteneur infra pour maintenir ce namespace ouvert. Les membres peuvent ensuite communiquer entre eux via 127.0.0.1, sans réseau défini par l’utilisateur ni service discovery.
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 infra. Le conteneur web accède désormais à Redis via 127.0.0.1:6379 et non via app-cache:6379. Deux règles découlent du namespace 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 s’exécute actuellement (les anciens paquets l’appellent podman generate kube), et podman kube play app.yaml le recrée sur un autre hôte. Quadlet dispose d’un type d’unité .kube qui exécute un tel fichier comme service systemd. Il s’agit d’une manière réellement différente de regrouper les services. C’est la principale raison 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 un véritable service systemd au démarrage. Les fichiers se placent 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 c’est l’en-tête de section qui crée le volume :
[Volume]systemctl --user daemon-reload
systemctl --user start caddy
systemctl --user status caddy
journalctl --user -u caddy -n 50Le nom du service vient 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.. C’est la section [Install] qui démarre le conteneur au démarrage, 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 à presque tout le monde :
sudo loginctl enable-linger deploy
loginctl show-user deploy --property=LingerVous devez obtenir 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 démarrage. Si les conteneurs disparaissent lorsque vous vous déconnectez, c’est toujours la cause.
Comme le conteneur est 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 gérez avec systemd. Cette fonctionnalité nécessite cgroup v2 (version 2 des control groups), qu’Ubuntu utilise par défaut depuis 22.04. Confirmez-le 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 dans le registry si une image plus récente existe 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 modifications qui seraient appliquées. L’ancienne commande podman generate systemd existe toujours, mais elle est obsolète. Utilisez donc des quadlets pour toute nouvelle configuration.
Où l’alias docker fonctionne et 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 nombreux, mais plus important. Le mode Swarm n’a pas d’équivalent, donc une stack Swarm ne peut pas être déployée. Les outils qui communiquent avec le socket Docker doivent utiliser le socket Podman exporté, et certains détectent encore 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 assure cette fonction. Le stockage est distinct : Podman ne peut 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 très sollicité.
Migrer une stack en cours d’exécution, étape par étape
- Créez ou choisissez l’utilisateur non privilégié qui possédera les conteneurs, puis vérifiez qu’une plage lui est attribuée dans
/etc/subuid. - Téléchargez de nouveau tout ce qui provient d’un registry, en utilisant des noms pleinement 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 définissez
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 : ni le stockage des images ni les réseaux. Vous pouvez donc les exécuter tous les deux pendant la migration. Le seul conflit possible concerne 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 installer sur votre VPS ?
Restez sur Docker si votre stack repose sur des fichiers Compose également maintenus par d’autres personnes, ou si vous dépendez d’outils qui communiquent avec le socket Docker. La compatibilité avec les fichiers et pratiques utilisés par tout le monde est un véritable avantage, et Docker en offre davantage. Une équipe dont les laptops utilisent tous Docker bénéficie aussi concrètement du fait d’exécuter le 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 sous son propre utilisateur non privilégié, sans aucun groupe docker sur la machine. L’alignement avec la distribution compte également : RHEL et ses reconstructions fournissent Podman comme moteur pris en charge. Sur ces systèmes, Podman est donc le choix qui réserve le moins de surprises. Si vous supervisez déjà tout le reste avec des unités systemd, les quadlets vous sembleront être la pièce manquante qui arrive naturellement, plutôt qu’un nouvel outil à apprendre.
Une option intermédiaire mérite d’être mentionnée. Podman rootful se comporte beaucoup comme Docker, conserve la commande docker grâce au wrapper et supprime malgré tout le daemon exécuté en permanence. Il renonce aussi à l’exécution rootless, qui est la partie modifiant votre niveau de sécurité. Considérez donc cette option comme une étape intermédiaire.
Si vous construisez encore votre premier hôte de conteneurs, le parcours d’installation et de sécurisation de Docker sur un VPS fraîchement installé est la voie la plus courte. Ces connaissances ne seront pas perdues. Les images et les volumes sont les mêmes objets avec les deux moteurs. Une migration modifie donc la manière dont vos services sont supervisés, et très peu d’autres éléments.
FAQ
Podman remplace-t-il Docker sans modification ?
Pour les commandes que vous saisissez, presque. L’installation de podman-docker fournit un wrapper /usr/bin/docker, et run, ps, build, logs et exec fonctionnent de la même manière. Il ne remplace pas le 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 sans session 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 hôte, puis mappe l’UID 1 du conteneur et les UID suivants vers votre plage subuid. Avec une plage qui commence à 100000, l’UID 1000 du conteneur devient 100999 sur l’hôte. Corrigez-le depuis l’espace de noms avec podman unshare chown 1000:1000 /path/to/data, utilisez l’option :U lors du premier montage, 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 problèmes peuvent subsister 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é au lieu de celles de root. C’est utile, et c’est pourquoi le groupe docker, équivalent à root, n’a pas d’équivalent avec Podman rootless. Ce mode ne corrige 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 durcissement que vous appliqueriez sur n’importe quel serveur.