Podman est un moteur de conteneurs sans démon obligatoire (daemonless), pensé pour une utilisation Linux native : rootless par défaut, intégration propre à systemd via Quadlet, et support des pods façon Kubernetes. Sur macOS et Windows, il s'appuie sur Podman Machine (une VM Linux). Ce guide couvre la version stable 5.8 et signale les changements de Podman 6.
Cette page présente la formation en trois temps : un modèle mental de l'outil, un démarrage rapide de cinq minutes, puis un aperçu des trois piliers (rootless, réseau, systemd) qui renvoie vers la leçon qui détaille chacun. Le plan des modules et l'état des versions par distribution viennent ensuite.
Le modèle mental Podman
Section intitulée « Le modèle mental Podman »Trois décisions d'architecture expliquent l'essentiel des différences que vous constaterez avec Docker, et donc l'essentiel des surprises. Elles ne sont pas indépendantes : c'est parce que Podman n'a pas de démon qu'un conteneur peut appartenir à votre session utilisateur, et c'est parce qu'il appartient à votre session que systemd peut en prendre la charge comme de n'importe quel autre processus. Gardez cet enchaînement en tête, il rend prévisible le comportement de l'outil.
- Rootless : les conteneurs tournent avec les droits de votre utilisateur, via les user namespaces.
- Daemonless : pas de service central permanent pour gérer vos conteneurs au quotidien.
- Systemd-first : avec Quadlet, un conteneur devient un service propre (restart, logs, dépendances).
- Bonus, pods et kube play : regrouper des conteneurs façon Kubernetes et lancer des manifests sans cluster.
Pour le « pourquoi » et les détails d'architecture, voir les concepts Podman.
Démarrer en 5 minutes
Section intitulée « Démarrer en 5 minutes »Objectif : valider que Podman tourne, savoir exposer un port, lire des logs, monter un volume, puis nettoyer.
Deux détails de cette séquence n'ont pas d'équivalent sous Docker. Le nom d'image est complet (docker.io/library/nginx) parce que Podman ne préfixe pas silencieusement les noms courts : il vous demandera de choisir un registre si vous écrivez seulement nginx. Et le suffixe :Z sur le volume demande à Podman de réétiqueter le répertoire pour SELinux ; sans lui, sur Fedora ou RHEL, le conteneur lira un permission denied alors que les droits Unix sont corrects.
La référence d'image porte aussi un tag et un digest : nginx:1.31.6-alpine@sha256:…. Le tag dit quelle version vous lisez, le digest garantit que vous tirez exactement l'image que ce guide a testée, même si l'éditeur republie un jour le même tag avec un autre contenu. Ce digest se relit au registre avec skopeo inspect, présenté dans le guide Skopeo.
# Vérifier la versionpodman version
# Lancer un conteneur nginxpodman run -d --name web -p 8080:80 \ docker.io/library/nginx:1.31.6-alpine@sha256:df221db836e1754089190208cee7eeda94f233197056426eda74a43ab1abeac2
# Vérifier qu'il tournecurl http://localhost:8080
# Voir les logspodman logs web
# Tester un volume (SELinux : :Z) : le dossier doit existermkdir -p ~/html && echo ok > ~/html/index.htmlpodman run -d --name web2 -v ~/html:/usr/share/nginx/html:Z -p 8081:80 \ docker.io/library/nginx:1.31.6-alpine@sha256:df221db836e1754089190208cee7eeda94f233197056426eda74a43ab1abeac2
# Vérifier le volume : la page servie est votre fichiercurl http://localhost:8081
# Nettoyerpodman rm -f web web2Si Podman n'est pas encore installé, commencez par l'installation ; pour comprendre ce qui vient de se passer, les concepts expliquent rootless, daemonless et pods, et les commandes de base donnent les gestes indispensables.
Podman vs Docker : choisir sans se mentir
Section intitulée « Podman vs Docker : choisir sans se mentir »L'objectif n'est pas une comparaison exhaustive, mais de vous aider à décider vite, puis de vous envoyer vers les guides.
Le schéma résume la différence d'architecture qui fonde tout le reste : chez
Docker, la commande passe par le daemon dockerd, qui tourne en root ;
chez Podman, chaque conteneur est suivi par un petit processus conmon
lancé avec les droits de l'utilisateur.
Choisissez Podman si…
Section intitulée « Choisissez Podman si… »Ces quatre situations ont un point commun : elles portent sur l'exploitation de la machine, pas sur le développement de l'application. Podman se distingue quand vous devez rendre des comptes sur qui exécute quoi, sur un serveur que vous administrez.
- Vous voulez rootless par défaut (multi-utilisateurs, postes partagés, moindre risque).
- Vous voulez des services systemd propres (serveur, self-hosting, edge).
- Vous voulez rester proche de Kubernetes (pods,
kube play,kube generate). - Vous voulez éviter un daemon central obligatoire sur la machine.
Restez sur Docker si…
Section intitulée « Restez sur Docker si… »À l'inverse, deux contextes rendent la migration plus coûteuse que le bénéfice attendu. Dans les deux cas, l'obstacle n'est pas technique mais lié à l'outillage autour : podman compose délègue à docker-compose ou à podman-compose selon ce qui est installé, et les comportements divergent sur les fichiers Compose élaborés.
- Votre quotidien dépend d'un Docker Compose complexe et d'intégrations « Docker-only ».
- Vous êtes dans un contexte où l'écosystème Docker Desktop est imposé.
Pilier 1, rootless en pratique
Section intitulée « Pilier 1, rootless en pratique »Le mode rootless est le gros avantage de Podman, mais il a des implications concrètes.
Ce qui se passe réellement
Section intitulée « Ce qui se passe réellement »Podman utilise les user namespaces pour mapper les identités du conteneur vers des UID/GID non privilégiés sur l'hôte :
Hôte Conteneur─────────────────────────────────────────────UID 1000 (vous) UID 0 (root)UID 100000-165535 (subuid) UID 1-65535Les trois frictions les plus courantes
Section intitulée « Les trois frictions les plus courantes »Ces trois frictions découlent toutes du même fait : votre conteneur croit être root, l'hôte sait qu'il ne l'est pas. Lisez la colonne Symptôme en premier, c'est le message d'erreur exact que vous verrez. La plus fréquente en pratique est celle des volumes. Le root du conteneur correspond à votre propre compte, ses fichiers vous appartiennent donc ; mais une image applicative tourne souvent sous un utilisateur non root, par exemple l'UID 1000, et ses fichiers apparaissent sur l'hôte avec un UID de votre plage subuid (100999 avec le mappage ci-dessus), illisibles par votre compte tant que vous n'avez pas ajouté --userns=keep-id.
| Friction | Symptôme | Réflexe |
|---|---|---|
| Ports bas | bind: permission denied sur 80/443 | utiliser ≥1024 ou sysctl net.ipv4.ip_unprivileged_port_start |
| Volumes | fichiers root:root ou permission denied | --userns=keep-id ou options de volume |
| Réseau | DNS ou perf surprenants | réseau dédié + backend adapté |
Deux leçons traitent ces frictions à fond. Le run avancé explique les user namespaces, l'option --userns=keep-id et le durcissement d'un conteneur (capacités, options de sécurité), et la leçon volumes traite la persistance des données et les permissions des fichiers partagés avec l'hôte.
Pilier 2, réseau : pasta, netavark et DNS
Section intitulée « Pilier 2, réseau : pasta, netavark et DNS »En rootless, c'est souvent le réseau qui surprend. L'objectif : un réseau où les conteneurs se résolvent par nom avec une perf correcte. Depuis Podman 5, le backend par défaut est Netavark (avec Aardvark pour le DNS) et la pile réseau rootless par défaut est Pasta.
L'exemple ci-dessous crée un réseau nommé, y lance une base PostgreSQL appelée db, puis un client Alpine qui la joint par son nom. Le ping doit répondre 1 packets received : c'est Aardvark qui a résolu db en adresse IP. Sur le réseau par défaut, cette résolution par nom n'existe pas, d'où le réseau dédié. Le mot de passe de la base est lu depuis une variable du shell, pour ne pas l'écrire dans l'historique.
# Créer un réseau applicatif (résolution DNS par nom)podman network create app-network
# Un conteneur "db" et un client qui le joint par son nomexport PGPASS="$(openssl rand -base64 24)"podman run -d --network app-network --name db -e POSTGRES_PASSWORD="$PGPASS" \ docker.io/library/postgres:16.15-alpine@sha256:721873c34ceb9f8d8fc265984940dc982404c105f19ad51be9fdc5970a6080eapodman run --rm --network app-network \ docker.io/library/alpine:3.24@sha256:294b683cb724975bec92580e1e685676bd4b50bda910ddb8c51d4cabeaec77e6 \ ping -c 1 db
# Nettoyer avant la suite (le pod réutilise le nom db)podman rm -f db && podman network rm app-networkLa leçon réseaux Podman détaille le reste : le DNS interne, la publication des ports en rootless, l'isolation entre réseaux et une architecture multi-tier complète.
Pilier 3, systemd : Quadlet (recommandé)
Section intitulée « Pilier 3, systemd : Quadlet (recommandé) »Pour du « prod-like » sur un serveur Linux, Quadlet est le point d'inflexion : un conteneur devient un service (journalctl, restart, dépendances). C'est la voie recommandée par l'écosystème Podman ; l'ancien podman generate systemd est déprécié (toujours utilisable, mais ce n'est plus la direction).
Exemple minimal : nginx.container
Section intitulée « Exemple minimal : nginx.container »Un fichier .container est une unité systemd enrichie : systemd le lit, génère à la volée le service correspondant, et vous manipulez ensuite ce service avec les commandes habituelles. La section [Container] remplace les arguments de podman run, la section [Service] reste du systemd standard. L'image y est épinglée par digest @sha256:... : c'est le seul moyen de garantir qu'un redémarrage du service ne change pas l'image sous vos pieds. Pour monter de version, vous changez tag et digest ensemble, puis relancez le service.
[Container]Image=docker.io/library/nginx:1.31.6-alpine@sha256:df221db836e1754089190208cee7eeda94f233197056426eda74a43ab1abeac2PublishPort=8080:80Volume=%h/nginx-html:/usr/share/nginx/html:ro,Z
[Service]Restart=always
[Install]WantedBy=default.targetActivation en rootless :
mkdir -p ~/.config/containers/systemd ~/nginx-html# placer nginx.container dans ce dossier
systemctl --user daemon-reloadsystemctl --user start nginxjournalctl --user -u nginx -fIl n'y a pas de systemctl --user enable : sur une unité générée par Quadlet, la commande échoue (Unit … is transient or generated). Le démarrage automatique est déjà déclaré par la section [Install] du fichier, WantedBy=default.target, que daemon-reload prend en compte. %h désigne votre répertoire personnel : en rootless, un chemin comme /data appartient à root et n'est pas accessible. Enfin, un service rootless s'arrête quand vous fermez votre session et ne démarre pas au boot, sauf si vous activez le maintien de session avec loginctl enable-linger. La leçon Quadlet et systemd couvre ce point et tout ce qu'un vrai service demande : dépendances entre unités, secrets, healthchecks, et les fichiers .pod et .volume.
Bonus, pods et intégration Kubernetes (sans cluster)
Section intitulée « Bonus, pods et intégration Kubernetes (sans cluster) »Quand vous avez besoin d'un modèle mental « mini-Kubernetes » sans déployer de cluster, les pods Podman sont parfaits.
Pod : web + base de données
Section intitulée « Pod : web + base de données »Le point à comprendre est la publication des ports. Elle se déclare une seule fois, sur le pod, jamais sur les conteneurs qui le rejoignent : ces derniers partagent la même pile réseau et se joignent entre eux par localhost. C'est exactement le comportement d'un Pod Kubernetes, et c'est ce qui explique pourquoi la chaîne de connexion de l'application pointe sur localhost:5432 alors que la base tourne dans un autre conteneur.
Le mot de passe est lu depuis une variable du shell plutôt qu'écrit dans la commande : sans cette précaution, il finit dans l'historique du shell. La valeur reste toutefois lisible dans la sortie de podman inspect, puisqu'elle devient une variable d'environnement du conteneur : pour un usage durable, préférez podman secret create et --secret.
L'image localhost/mon-app:1.0.0 représente votre application, construite au préalable avec podman build -t mon-app:1.0.0 . comme l'enseigne la leçon build. Podman range une image construite localement sous le préfixe localhost/, et lui donner une version explicite plutôt que latest vous dit toujours quel code tourne dans le pod.
export PGPASS="$(openssl rand -base64 24)"
podman pod create --name webapp -p 8082:80
podman run -d --pod webapp --name db \ -e POSTGRES_PASSWORD="$PGPASS" \ docker.io/library/postgres:16.15-alpine@sha256:721873c34ceb9f8d8fc265984940dc982404c105f19ad51be9fdc5970a6080ea
podman run -d --pod webapp --name app \ -e DATABASE_URL="postgres://postgres:$PGPASS@localhost:5432" \ localhost/mon-app:1.0.0podman kube play : exécuter un manifest sans cluster
Section intitulée « podman kube play : exécuter un manifest sans cluster »La commande podman kube play lit un manifest Kubernetes (Pod, Deployment, ConfigMap, Secret) et crée les pods et conteneurs correspondants sur votre machine, sans cluster. L'inverse existe : podman kube generate exporte un pod que vous avez construit à la main en manifest réutilisable, ce qui fait de Podman un bon banc d'essai avant Kubernetes.
# Lancer un manifest Kubernetes localementpodman kube play deployment.yaml
# Arrêter et supprimerpodman kube play deployment.yaml --down
# Exporter un pod existant vers un manifest Kubernetespodman kube generate webapp > webapp.yamlLes commandes historiques podman play kube et podman generate kube restent des alias, mais la forme moderne est podman kube play / podman kube generate. Toutes les ressources Kubernetes ne sont pas prises en charge : la leçon pods précise le rôle du conteneur d'infrastructure et les limites du modèle, et la leçon kube play liste ce que la commande accepte réellement d'un manifest.
macOS / Windows : Podman Machine
Section intitulée « macOS / Windows : Podman Machine »Sur macOS et Windows, les conteneurs Linux nécessitent une VM, car ils dépendent du kernel Linux. Podman Machine la gère pour vous : podman machine init crée la VM, podman machine start la démarre, et la commande podman de votre poste parle ensuite à cette VM de façon transparente. podman machine ssh ouvre un shell dans la VM quand vous devez y diagnostiquer quelque chose.
podman machine initpodman machine startpodman run -d -p 8080:80 \ docker.io/library/nginx:1.31.6-alpine@sha256:df221db836e1754089190208cee7eeda94f233197056426eda74a43ab1abeac2podman machine sshLes prérequis propres à chaque système (WSL 2 ou Hyper-V sous Windows, installation par Homebrew sous macOS) sont détaillés pas à pas dans la leçon installation.
Comment la formation Podman est-elle organisée ?
Section intitulée « Comment la formation Podman est-elle organisée ? »La formation suit l'ordre dans lequel on s'approprie l'outil : comprendre ses principes, exécuter des conteneurs, construire des images, les regrouper en pods, puis les industrialiser avec systemd. Chaque module s'appuie sur le précédent, et votre avancement leçon par leçon se suit sur la page Mon parcours.
Découvrir Podman
Section intitulée « Découvrir Podman »Le premier module explique ce qui distingue Podman de Docker. Les
concepts posent le rootless, l'absence de démon, les pods et
l'architecture, l'installation vous rend opérationnel sur
Linux, macOS ou Windows, et les commandes de base (run,
ps, exec, logs, images, nettoyage) font le quotidien.
Exécuter des conteneurs
Section intitulée « Exécuter des conteneurs »Le deuxième module va au-delà du simple lancement. Le run avancé détaille les namespaces, la sécurité et les options d'exécution, les volumes traitent la persistance et les permissions, et les réseaux couvrent le DNS, les ports, l'isolation et les architectures multi-tier.
Construire ses images
Section intitulée « Construire ses images »Le troisième module produit les images. Le guide build enseigne le Containerfile, le multi-stage et l'optimisation, puis les manifests multi-arch publient une même image pour plusieurs architectures.
Pods et passerelle vers Kubernetes
Section intitulée « Pods et passerelle vers Kubernetes »Le pod est natif chez Podman, comme dans Kubernetes. Les pods expliquent le conteneur d'infrastructure et la correspondance avec Kubernetes, et kube play déploie des manifests sans cluster, ce qui prépare le passage à l'orchestrateur.
Industrialiser avec systemd
Section intitulée « Industrialiser avec systemd »Le dernier module transforme un conteneur en service. Quadlet apporte le démarrage automatique, le redémarrage et la journalisation de systemd. Il arrive en dernier parce qu'il suppose acquis le contenu des réseaux et des volumes : une unité systemd déclare les deux d'avance, sans possibilité de tâtonner en ligne de commande.
Versions et distributions
Section intitulée « Versions et distributions »Podman évolue vite et chaque distribution embarque une version différente. La branche stable de référence pour ce guide est la 5.8 (dernière 5.x au 13 octobre 2026 : 5.8.9, publiée le 12 octobre 2026). La 6.0.0 est sortie le 24 juin 2026 ; la branche courante est la 6.1 (dernière version : 6.1.4, publiée le 12 octobre 2026). Elle arrive d'abord sur les distributions rolling et via Homebrew.
Ce tableau sert à répondre à une seule question avant de suivre un guide : la commande que vous lisez existe-t-elle dans votre version ? L'écart est considérable, plus de trois ans séparent la 4.3 de Debian 12 de la 6.1 d'Arch, et des pans entiers de la documentation amont ne s'appliquent pas aux versions 4.x, à commencer par Quadlet, absent avant la 4.4.
| Base | Version Podman |
|---|---|
| Arch Linux, Homebrew (macOS) | 6.1 |
| Amont (dernières, 13 octobre 2026) | 5.8.9 (5.x) / 6.1.4 |
| Fedora 43 (à jour) | 5.8 |
| Debian 13, Ubuntu 25.10 | 5.4 |
| Fedora 42 | 5.4 |
| Ubuntu 24.04 LTS | 4.9 |
| Debian 12 | 4.3 |
Pour un environnement critique, épinglez la version et testez les comportements par défaut, qui changent d'une majeure à l'autre (réseau, volumes, cgroups).
Podman 6 : ce qui change
Section intitulée « Podman 6 : ce qui change »Sortie le 24 juin 2026, Podman 6 finalise plusieurs suppressions entamées en 5.x. Elle exige des composants compagnons alignés : Buildah 1.44, Skopeo 1.23, Netavark/Aardvark 2.0. Le dépôt a par ailleurs rejoint l'organisation CNCF podman-container-tools.
La CVE-2026-57231 est souvent citée comme argument de migration, à tort. Cette fuite de variables d'environnement de l'hôte vers le conteneur, déclenchée par une image contenant une variable sans valeur, touche toutes les versions de la 1.8.1 à la 5.8.3 incluse et pèse 7.5 sur l'échelle CVSS v3.1. Elle est corrigée à la fois dans la 5.8.4 et dans la 6.0.0 : rester sur la branche 5.x à jour suffit à s'en protéger, la montée en 6 se décide sur d'autres critères.
Ruptures qui cassent des setups
Section intitulée « Ruptures qui cassent des setups »Ces suppressions ne sont pas des surprises : chacune concerne un composant déjà remplacé par défaut depuis Podman 4 ou 5, et déjà signalé comme déprécié. Le risque porte donc sur les machines qui ont été configurées explicitement pour l'ancien composant, typiquement un /etc/containers/containers.conf qui force network_backend = "cni". Vérifiez ce fichier avant toute montée de version, ainsi que vos scripts : la dernière ligne de la liste, sur le changement de logique des filtres, casse silencieusement un podman ps --filter qui en cumulait plusieurs.
- CNI supprimé au profit de Netavark (le backend réseau par défaut depuis Podman 5).
- slirp4netns supprimé au profit de Pasta pour le réseau rootless.
- cgroups v1 et iptables supprimés (passez à cgroups v2 et nftables).
- BoltDB supprimé : migration automatique vers SQLite au premier démarrage.
- Intel Mac et Windows 10 ne sont plus supportés.
podman volume prunene supprime plus que les volumes anonymes par défaut ; utilisez--allpour l'ancien comportement.- Quadlet range désormais chaque unité et ses fichiers dans un sous-répertoire au lieu du fichier
.app. - Filtres multiples : ils se combinent maintenant en ET et non plus en OU, ce qui restreint les résultats d'un
--filtercumulé.
Nouveautés utiles
Section intitulée « Nouveautés utiles »Ces ajouts répondent tous à des frustrations connues de la 5.x. Les deux premiers rendent le nettoyage prévisible, ce qui manquait cruellement : jusqu'ici, podman volume prune supprimait sans prévisualisation possible. Le troisième donne enfin une vue directe des unités Quadlet, qu'il fallait auparavant reconstituer en listant les fichiers et les services systemd séparément.
podman volume prune --dry-runet--allpour un nettoyage maîtrisé.podman quadlet ls(nouvel alias) avec une colonne Pod.podman infoexpose les répertoires CDI et les périphériques découverts.--gpuscompatible AMD, adresses IP statiques multiples, routesblackhole/unreachable.podman machine os updateet gestion multi-provider des VM.
Autour de Podman
Section intitulée « Autour de Podman »Podman ne construit pas d'images et ne parle pas aux registres tout seul : ces fonctions vivent dans des binaires distincts, qui partagent la même bibliothèque de stockage et les mêmes fichiers de configuration. Vous pouvez donc construire une image avec Buildah, la copier avec Skopeo et l'exécuter avec Podman sans jamais la pousser sur un registre intermédiaire.
Trois pages prolongent Podman sur ce point : Buildah (construire des images sans daemon, workflows avancés), Skopeo (copier et inspecter des images entre registries) et Conteneurisation (fondamentaux des conteneurs Linux et de l'écosystème OCI).
Mettre en pratique
Section intitulée « Mettre en pratique »Avant de dérouler le parcours, validez le geste de base : ce lab vous fait lancer un conteneur en mode détaché avec Podman, contrôler son état et consulter ses logs sur une VM, sans rien installer sur votre poste.
FAQ : questions fréquentes
Section intitulée « FAQ : questions fréquentes »Les questions rassemblées ici sont celles que pose une équipe qui vient de Docker : équivalences de commandes, sort de Compose, et comportement du mode rootless sur les ports privilégiés. Chaque réponse tient en quelques lignes et renvoie vers le guide détaillé quand le sujet le mérite.
Une alternative daemonless et rootless à Docker
Podman (POD MANager) est un moteur de conteneurs open source conforme à OCI, créé par Red Hat et désormais hébergé par la CNCF. Il exécute des conteneurs et des pods, avec deux différences clés face à Docker :- Daemonless : pas de démon central, chaque commande lance directement le conteneur.
- Rootless : les conteneurs tournent avec vos droits utilisateur, sans root.
alias docker=podman fonctionne dans la majorité des cas). Il ajoute l'intégration systemd via Quadlet et le support des pods façon Kubernetes.podman run -d -p 8080:80 \
docker.io/library/nginx:1.31.6-alpine@sha256:df221db836e1754089190208cee7eeda94f233197056426eda74a43ab1abeac2
Une majeure surtout faite de suppressions
Podman 6 est sortie fin juin 2026. Elle finalise des retraits entamés en 5.x :| Supprimé | Remplacé par |
|---|---|
| CNI | Netavark |
| slirp4netns | Pasta |
| cgroups v1 | cgroups v2 |
| iptables | nftables |
| BoltDB | SQLite (migration auto) |
podman volume prune ne supprime plus que les volumes anonymes par défaut (--all pour tout), Quadlet range ses fichiers en sous-répertoires, et les compagnons doivent être alignés (Buildah 1.44, Skopeo 1.23, Netavark/Aardvark 2.0).Aujourd'hui, seuls Arch et Homebrew livrent la 6.0 ; Fedora et Debian restent en 5.x.dockerd.Comparaison architecturale
Docker : Client → dockerd (root) → containerd → runc → conteneur
Podman : Client → conmon (user) → runc → conteneur
Avantages du daemonless
| Aspect | Docker (daemon) | Podman (daemonless) |
|---|---|---|
| Point de défaillance | dockerd = SPOF | Pas de SPOF |
| Redémarrage service | Tue tous les conteneurs | Conteneurs indépendants |
| Surface d'attaque | Daemon root exposé | Pas de daemon |
| Ressources | Daemon permanent | Processus à la demande |
En pratique
# Pas de service à démarrer
# Crée directement le processus
podman run -d docker.io/library/nginx:1.31.6-alpine@sha256:df221db836e1754089190208cee7eeda94f233197056426eda74a43ab1abeac2
# Chaque conteneur est un processus indépendant
ps aux | grep conmon
# user 1234 conmon --cid abc123...
# user 5678 conmon --cid def456...
→ Concepts PodmanPrincipe de fonctionnement
┌─────────────────────────────────────────────────────────┐
│ HÔTE │
│ Utilisateur bob (UID 1000) │
│ └── Plage subuid : 100000-165535 │
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ CONTENEUR │ │
│ │ root (UID 0) → mappé sur UID 100000 (hôte) │ │
│ │ user (UID 1000) → mappé sur UID 101000 (hôte) │ │
│ └─────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
Configuration
# Vérifier les plages subuid/subgid
cat /etc/subuid
# bob:100000:65536
# Initialiser le user namespace
podman system migrate
# Lancer un conteneur rootless
podman run -d docker.io/library/nginx:1.31.6-alpine@sha256:df221db836e1754089190208cee7eeda94f233197056426eda74a43ab1abeac2
Sécurité
| Scénario | Impact rootless |
|---|---|
| Évasion du conteneur | Attaquant = UID 100000 (non privilégié) |
| Accès au socket | Pas de socket root exposé |
| Montage /etc/passwd | Impossible sans droits |
Ce que partagent les conteneurs d'un pod
| Ressource | Partagée ? | Implication |
|---|---|---|
| Namespace réseau | ✅ Oui | Tous les conteneurs utilisent localhost |
| Namespace IPC | ✅ Oui | Mémoire partagée possible |
| Namespace UTS | ✅ Oui | Même hostname |
| Volumes | ⚙️ Configurable | Partage de données |
| Namespace PID | ❌ Non | Processus isolés |
Exemple pratique
# Créer un pod avec un port exposé
podman pod create --name webapp -p 8080:80
# Mot de passe généré, jamais écrit en clair dans la commande
export PGPASS="$(openssl rand -base64 24)"
# Ajouter une BDD (accessible via localhost dans le pod)
podman run -d --pod webapp \
--name db \
-e POSTGRES_PASSWORD="$PGPASS" \
docker.io/library/postgres:16.15-alpine@sha256:721873c34ceb9f8d8fc265984940dc982404c105f19ad51be9fdc5970a6080ea
# Ajouter l'app (accède à la BDD via localhost:5432)
podman run -d --pod webapp \
--name app \
-e DATABASE_HOST=localhost \
localhost/mon-app:1.0.0
Étape 1 : Installer Podman
# Ubuntu/Debian
sudo apt install podman
# Fedora
sudo dnf install podman
Étape 2 : Créer l'alias
# Dans ~/.bashrc ou ~/.zshrc
alias docker=podman
Étape 3 : Migrer les commandes
| Docker | Podman | Notes |
|---|---|---|
docker run |
podman run |
✅ Identique |
docker build |
podman build |
✅ Identique |
docker-compose |
podman-compose |
Installer via pip |
Dockerfile |
Containerfile |
Les deux acceptés |
Étape 4 : Docker Compose
# Option 1 : podman-compose
pip install podman-compose
podman-compose up -d
# Option 2 : podman compose (4.x+)
podman compose up -d
Ce qui fonctionne directement
- ✅ Images Docker Hub
- ✅ Dockerfile/Containerfile
- ✅ Volumes et réseaux
- ✅ Variables d'environnement
Différences mineures
- Registres multiples par défaut (pas seulement Docker Hub)
- Socket différent (
/run/podman/podman.sock) - Mode rootless par défaut
Ports ≥ 1024 (fonctionnent directement)
# Aucune configuration spéciale
podman run -d -p 8080:80 docker.io/library/nginx:1.31.6-alpine@sha256:df221db836e1754089190208cee7eeda94f233197056426eda74a43ab1abeac2
curl http://localhost:8080
Ports < 1024 (nécessitent configuration)
# Par défaut, erreur :
podman run -d -p 80:80 docker.io/library/nginx:1.31.6-alpine@sha256:df221db836e1754089190208cee7eeda94f233197056426eda74a43ab1abeac2
# Error: rootlessport cannot expose privileged port 80
Solution 1 : Modifier sysctl# Autoriser les ports à partir de 80
sudo sysctl -w net.ipv4.ip_unprivileged_port_start=80
# Rendre permanent
echo 'net.ipv4.ip_unprivileged_port_start=80' | sudo tee /etc/sysctl.d/podman-ports.conf
Solution 2 : garder un port hautPubliez le conteneur sur un port ≥ 1024 (-p 8080:80) et placez devant lui un reverse proxy de l'hôte, ou une redirection de port du pare-feu, qui écoute sur 80 ou 443. Aucun réglage du noyau n'est alors nécessaire.Tableau récapitulatif
| Port | Mode rootless | Solution |
|---|---|---|
| ≥ 1024 | ✅ Direct | -p 8080:80 |
| < 1024 | ⚠️ Bloqué | sysctl ou port haut + reverse proxy |
| 443 (HTTPS) | ⚠️ Bloqué | sysctl ou reverse proxy |
podman build utilise Buildah en interne pour la construction d'images.Architecture de build
podman build ─────┐
▼
Buildah (bibliothèque)
│
▼
Image OCI
Différences d'usage
| Outil | Usage | Commande type |
|---|---|---|
| podman build | Build via Containerfile | podman build -t app . |
| buildah | Build interactif ou scripté | buildah from, buildah run, buildah commit |
Exemple Buildah interactif
# Créer un conteneur de travail
ctr=$(buildah from docker.io/library/alpine:3.24@sha256:294b683cb724975bec92580e1e685676bd4b50bda910ddb8c51d4cabeaec77e6)
# Exécuter des commandes
buildah run $ctr apk add --no-cache curl jq
# Configurer l'image
buildah config --entrypoint '["curl"]' $ctr
# Committer l'image
buildah commit $ctr mon-curl:1.0
Quand utiliser Buildah directement ?
- Scripts de build complexes sans Dockerfile
- Modification d'images existantes
- Pipelines CI/CD avec contrôle fin
- Images minimales construites instruction par instruction
Diagnostic
# Vérifier les permissions
podman run --rm -v ~/data:/data docker.io/library/alpine:3.24@sha256:294b683cb724975bec92580e1e685676bd4b50bda910ddb8c51d4cabeaec77e6 ls -la /data
# Permission denied → problème d'UID ou SELinux
Solution 1 : Option :U (ajustement UID)
# :U ajuste automatiquement le propriétaire pour le mapping rootless
podman run -v ~/data:/data:U localhost/mon-app:1.0.0
Solution 2 : SELinux avec :Z ou :z
# :Z = label privé (un seul conteneur)
podman run -v ~/data:/data:Z localhost/mon-app:1.0.0
# :z = label partagé (plusieurs conteneurs)
podman run -v ~/shared:/shared:z localhost/mon-app:1.0.0
Solution 3 : keep-id
# Votre UID (1000) devient l'UID 1000 dans le conteneur
podman run --userns=keep-id -v ~/data:/data localhost/mon-app:1.0.0
Tableau des options
| Option | Problème résolu | Effet |
|---|---|---|
:U |
UID rootless | Chown récursif |
:Z |
SELinux | Label privé |
:z |
SELinux partagé | Label partagé |
--userns=keep-id |
Mapping UID | Conserve votre UID |
:ro |
Sécurité | Lecture seule |
Générer un manifest depuis un conteneur/pod
# Depuis un conteneur unique
podman run -d --name web -p 8080:80 docker.io/library/nginx:1.31.6-alpine@sha256:df221db836e1754089190208cee7eeda94f233197056426eda74a43ab1abeac2
podman kube generate web > web-pod.yaml
# Depuis un pod complet
# Mot de passe de la base, généré et gardé hors de l'historique
export PGPASS="$(openssl rand -base64 24)"
podman pod create --name webapp -p 8080:80
podman run -d --pod webapp --name db -e POSTGRES_PASSWORD="$PGPASS" \
docker.io/library/postgres:16.15-alpine@sha256:721873c34ceb9f8d8fc265984940dc982404c105f19ad51be9fdc5970a6080ea
podman run -d --pod webapp --name app localhost/mon-app:1.0.0
podman kube generate webapp > webapp.yaml
Exemple de manifest généré
apiVersion: v1
kind: Pod
metadata:
name: webapp
spec:
containers:
- name: db
image: docker.io/library/postgres:16.15-alpine@sha256:721873c34ceb9f8d8fc265984940dc982404c105f19ad51be9fdc5970a6080ea
- name: app
image: localhost/mon-app:1.0.0
Exécuter un manifest Kubernetes localement
# Déployer un manifest existant
podman kube play webapp.yaml
# Avec un ConfigMap
podman kube play webapp.yaml --configmap config.yaml
# Arrêter et supprimer
podman kube play webapp.yaml --down
Workflow dev → prod
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Dev local │ ──▶ │ generate │ ──▶ │ Kubernetes │
│ (podman) │ │ kube │ │ (prod) │
└─────────────┘ └─────────────┘ └─────────────┘
→ Concepts podsLimiter la mémoire
# Limite à 512 Mo de RAM
podman run -d --name web --memory 512m docker.io/library/nginx:1.31.6-alpine@sha256:df221db836e1754089190208cee7eeda94f233197056426eda74a43ab1abeac2
# Limite RAM + swap
podman run -d --memory 512m --memory-swap 1g docker.io/library/nginx:1.31.6-alpine@sha256:df221db836e1754089190208cee7eeda94f233197056426eda74a43ab1abeac2
Limiter le CPU
# Limite à 1.5 cœurs CPU
podman run -d --cpus 1.5 docker.io/library/nginx:1.31.6-alpine@sha256:df221db836e1754089190208cee7eeda94f233197056426eda74a43ab1abeac2
# Limite en millicores (comme Kubernetes)
podman run -d --cpus 0.5 docker.io/library/nginx:1.31.6-alpine@sha256:df221db836e1754089190208cee7eeda94f233197056426eda74a43ab1abeac2 # 500m
# Limiter à certains cœurs spécifiques
podman run -d --cpuset-cpus 0,1 docker.io/library/nginx:1.31.6-alpine@sha256:df221db836e1754089190208cee7eeda94f233197056426eda74a43ab1abeac2
Limiter les processus
# Maximum 100 processus
podman run -d --pids-limit 100 docker.io/library/nginx:1.31.6-alpine@sha256:df221db836e1754089190208cee7eeda94f233197056426eda74a43ab1abeac2
Vérifier les limites
# Statistiques en temps réel
podman stats
# Inspecter les limites configurées
podman inspect web --format '{{.HostConfig.Memory}}'
Tableau récapitulatif
| Option | Effet | Exemple |
|---|---|---|
--memory |
Limite RAM | --memory 256m |
--memory-swap |
Limite RAM+swap | --memory-swap 512m |
--cpus |
Limite cœurs | --cpus 2 |
--cpuset-cpus |
Cœurs spécifiques | --cpuset-cpus 0,2 |
--pids-limit |
Limite processus | --pids-limit 50 |
Réseau par défaut : pas de DNS
# Mot de passe de la base, généré et gardé hors de l'historique
export PGPASS="$(openssl rand -base64 24)"
# Le réseau 'podman' par défaut n'a pas de DNS
podman run -d --name db -e POSTGRES_PASSWORD="$PGPASS" \
docker.io/library/postgres:16.15-alpine@sha256:721873c34ceb9f8d8fc265984940dc982404c105f19ad51be9fdc5970a6080ea
podman run --rm docker.io/library/alpine:3.24@sha256:294b683cb724975bec92580e1e685676bd4b50bda910ddb8c51d4cabeaec77e6 ping db
# ping: bad address 'db' ← Échec !
Réseau personnalisé : DNS activé
# Retirer le conteneur db de l'essai précédent
podman rm -f db
export PGPASS="$(openssl rand -base64 24)"
# Créer un réseau avec DNS intégré
podman network create mon-reseau
# Lancer les conteneurs sur ce réseau
podman run -d --network mon-reseau --name db -e POSTGRES_PASSWORD="$PGPASS" \
docker.io/library/postgres:16.15-alpine@sha256:721873c34ceb9f8d8fc265984940dc982404c105f19ad51be9fdc5970a6080ea
podman run --rm --network mon-reseau docker.io/library/alpine:3.24@sha256:294b683cb724975bec92580e1e685676bd4b50bda910ddb8c51d4cabeaec77e6 ping db
# PING db (10.89.0.2): 56 data bytes ← Succès !
Alias DNS
export PGPASS="$(openssl rand -base64 24)"
# Ajouter des alias pour un conteneur
podman run -d --network mon-reseau \
--name postgres \
--network-alias db \
--network-alias database \
-e POSTGRES_PASSWORD="$PGPASS" \
docker.io/library/postgres:16.15-alpine@sha256:721873c34ceb9f8d8fc265984940dc982404c105f19ad51be9fdc5970a6080ea
# Accessible via 'postgres', 'db' ou 'database'
Tableau récapitulatif
| Réseau | DNS | Isolation |
|---|---|---|
podman (défaut) |
❌ Non | Minimale |
| Personnalisé | ✅ Oui | Par réseau |
host |
Hôte | Aucune |
none |
❌ Non | Totale |
podman generate systemd qui est déprécié.Créer un fichier Quadlet
# Rootless : ~/.config/containers/systemd/
# Rootful : /etc/containers/systemd/
mkdir -p ~/.config/containers/systemd ~/nginx-html
Exemple minimal : nginx.container
[Container]
Image=docker.io/library/nginx:1.31.6-alpine@sha256:df221db836e1754089190208cee7eeda94f233197056426eda74a43ab1abeac2
PublishPort=8080:80
Volume=%h/nginx-html:/usr/share/nginx/html:ro,Z
[Service]
Restart=always
[Install]
WantedBy=default.target
Activer le service
# Recharger systemd pour détecter le fichier
systemctl --user daemon-reload
# Démarrer le conteneur
systemctl --user start nginx.service
Il n'y a pas de systemctl --user enable : sur une unité générée par Quadlet, la commande échoue (Unit … is transient or generated). Le démarrage automatique est déclaré par la section [Install] du fichier (WantedBy=default.target), prise en compte au daemon-reload. En rootless, ajoutez loginctl enable-linger pour que le service démarre au boot sans session ouverte, et %h désigne votre répertoire personnel.Avantages de Quadlet
| Aspect | generate systemd | Quadlet |
|---|---|---|
| Maintenance | Régénérer si config change | Fichier déclaratif |
| Statut | Déprécié | Recommandé |
| Logs | podman logs |
journalctl |
| Restart | Configurable | Natif systemd |
Architecture Podman Machine
macOS/Windows VM Podman Machine
┌─────────────────┐ ┌─────────────────┐
│ podman CLI │ ◄─────────▶ │ Kernel Linux │
│ (commandes) │ socket │ + Podman │
│ │ │ + Conteneurs │
└─────────────────┘ └─────────────────┘
Commandes de base
# Initialiser la VM (télécharge l'image Fedora CoreOS)
podman machine init
# Démarrer la VM
podman machine start
# Vérifier le statut
podman machine list
# Se connecter à la VM
podman machine ssh
Options de virtualisation
| Plateforme | Backend par défaut | Alternative |
|---|---|---|
| macOS Intel | QEMU | HyperKit |
| macOS Apple Silicon | QEMU (arm64) | - |
| Windows | WSL 2 | Hyper-V |
Forwarding de ports
Les ports exposés par les conteneurs sont automatiquement accessibles sur localhost grâce au forwarding intégré.→ Installation PodmanArbre de décision
| Critère | → Podman | → Docker |
|---|---|---|
| Sécurité rootless | ✅ Natif | ⚠️ Expérimental |
| Intégration systemd | ✅ Quadlet | ❌ Workarounds |
| Pods Kubernetes | ✅ Natif | ❌ Non supporté |
| Docker Compose | ⚠️ Compat partielle | ✅ Natif |
| Écosystème existant | ⚠️ Migration | ✅ Déjà en place |
| Support enterprise | Red Hat | Docker Inc |
Recommandations
Choisir Podman si :- Environnement RHEL/Fedora/CentOS
- Sécurité rootless requise
- Services systemd (self-hosting)
- Préparation vers Kubernetes
- Équipe formée Docker Compose
- Intégrations tierces Docker-only
- Pas de contrainte rootless
En pratique
Beaucoup d'équipes utilisent les deux : Docker Desktop pour le dev, Podman pour la prod sur serveurs Linux.→ Comparaison DockerProblème 1 : Ports < 1024
# Erreur typique
podman run -p 80:80 docker.io/library/nginx:1.31.6-alpine@sha256:df221db836e1754089190208cee7eeda94f233197056426eda74a43ab1abeac2
# Error: rootlessport cannot expose privileged port 80
Solution :sudo sysctl -w net.ipv4.ip_unprivileged_port_start=80
Problème 2 : DNS entre conteneurs
# Le réseau par défaut n'a pas de DNS
podman run --rm docker.io/library/alpine:3.24@sha256:294b683cb724975bec92580e1e685676bd4b50bda910ddb8c51d4cabeaec77e6 ping other-container
# ping: bad address 'other-container'
Solution : créer un réseau personnalisé# Mot de passe de la base, généré et gardé hors de l'historique
export PGPASS="$(openssl rand -base64 24)"
podman network create mon-reseau
podman run -d --network mon-reseau --name db -e POSTGRES_PASSWORD="$PGPASS" \
docker.io/library/postgres:16.15-alpine@sha256:721873c34ceb9f8d8fc265984940dc982404c105f19ad51be9fdc5970a6080ea
podman run --rm --network mon-reseau docker.io/library/alpine:3.24@sha256:294b683cb724975bec92580e1e685676bd4b50bda910ddb8c51d4cabeaec77e6 ping db # ✅ fonctionne
Problème 3 : Performance réseau
slirp4netns, utilisé par défaut avant Podman 5, est plus lent que le bridge du noyau ; Podman 6 le supprime.Solution : utiliser pasta (défaut depuis Podman 5, à déclarer sur une 4.x)# ~/.config/containers/containers.conf
[network]
default_rootless_network_cmd = "pasta"
Tableau des backends réseau rootless
| Backend | Perf | DNS | Disponibilité |
|---|---|---|---|
| slirp4netns | Lente | ❌ | Partout |
| pasta | Rapide | ✅ | Podman 4.4+ |
podman kube play (ancienne forme podman play kube, restée en alias) permet de déployer des manifests Kubernetes localement sans avoir besoin d'un cluster.Cas d'usage
- Test local avant déploiement en prod
- Machines edge sans Kubernetes
- CI/CD pour valider les manifests
- Développement avec les mêmes fichiers que la prod
Exemple pratique
# webapp.yaml
apiVersion: v1
kind: Pod
metadata:
name: webapp
spec:
containers:
- name: web
image: docker.io/library/nginx:1.31.6-alpine@sha256:df221db836e1754089190208cee7eeda94f233197056426eda74a43ab1abeac2
ports:
- containerPort: 80
hostPort: 8080
# Déployer le manifest
podman kube play webapp.yaml
# Vérifier
podman pod ps
podman ps
curl http://localhost:8080
# Arrêter et supprimer
podman kube play webapp.yaml --down
Ressources Kubernetes supportées
| Ressource | Support |
|---|---|
| Pod | ✅ Complet |
| Deployment | ✅ Complet |
| ConfigMap | ✅ Complet |
| Secret | ✅ Complet |
| DaemonSet | ✅ Crée un pod |
| PersistentVolumeClaim | ✅ Crée un volume Podman |
| Service | ❌ Utilisez les ports du pod |
| Ingress | ❌ Non supporté |
podman.socket est un service optionnel qui expose l'API Docker pour les outils tiers (docker-compose, Portainer, etc.). Il s'active à la demande (socket activation) et ne tourne pas en permanence.subuid de l'hôte, par exemple 590823 pour l'UID 1000 du conteneur. Ce compte n'existant pas, vous ne pouvez plus lire vos propres données.Vérifiez le mapping avant de chercher ailleurs :podman unshare cat /proc/self/uid_map
Solutions, par ordre de préférence :--userns=keep-id: aligne votre UID réel avec le même UID dans le conteneur. C'est la réponse la plus propre pour un volume partagé avec votre compte.- Suffixe
:Usur le volume : Podman ajuste récursivement le propriétaire des fichiers. Efficace, mais il réécrit les permissions du répertoire hôte, donc à manier avec précaution. podman unshare chown: entrer dans le namespace pour corriger un propriétaire existant sans toucher au reste.
- slirp4netns : legacy, plus lent, compatibilité large
- pasta : défaut depuis Podman 5, plus performant, remplace slirp4netns
podman info --format '{{.Host.NetworkBackend}}'.- Compose : utilisez
podman-composeou le mode intégré - Réseau rootless : comportement différent (pas d'iptables)
- Certaines options
docker buildnon supportées - API : compatible via socket, mais pas identique à 100%
podman generate systemd créait des fichiers statiques qui devenaient obsolètes. Quadlet utilise des fichiers .container déclaratifs que systemd interprète dynamiquement. C'est plus maintenable et recommandé par Red Hat.sudo. Le chemin de stockage confirme la réponse, et c'est aussi lui qui explique pourquoi une image « disparaît » quand on change de mode.podman info --format '{{.Host.Security.Rootless}}'
# true = rootless, false = rootful
# Vérifier aussi le chemin de stockage
podman info --format '{{.Store.GraphRoot}}'
# ~/.local/share/containers/storage = rootless
# /var/lib/containers/storage = rootful