
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/Windows, il s’appuie sur Podman Machine (VM).
Le modèle mental Podman
Section intitulée « Le modèle mental Podman »Retenir 3 piliers (et 1 bonus) :
- Rootless : les conteneurs tournent avec les droits de ton utilisateur, via les user namespaces.
- Daemonless : pas besoin d’un service central permanent pour gérer tes conteneurs au quotidien.
- Systemd-first : avec Quadlet, un conteneur devient un service propre (restart, logs, dépendances). Bonus) Pods & play kube : regrouper des conteneurs “style Kubernetes” et lancer des manifests sans cluster.
Si tu veux le “pourquoi” et les détails d’architecture : Concepts
Démarrer en 5 minutes
Section intitulée « Démarrer en 5 minutes »Objectif : valider que Podman tourne, que tu sais exposer un port, lire des logs, monter un volume, puis nettoyer.
# Vérifier la versionpodman version
# Lancer un conteneur nginxpodman run -d --name web -p 8080:80 docker.io/library/nginx:alpine
# Vérifier qu'il tournecurl http://localhost:8080
# Voir les logspodman logs web
# Tester un volume (SELinux: :Z)podman run -d --name web2 -v ~/html:/usr/share/nginx/html:Z -p 8081:80 nginx:alpine
# Nettoyerpodman rm -f web web2Podman vs Docker : choisir sans se mentir
Section intitulée « Podman vs Docker : choisir sans se mentir »L’objectif ici n’est pas de refaire une comparaison exhaustive : c’est de t’aider à décider vite, puis de t’envoyer vers les guides.
Choisis Podman si…
Section intitulée « Choisis Podman si… »- Tu veux rootless par défaut (multi-utilisateurs, postes partagés, moindre risque).
- Tu veux des services systemd propres (serveur, self-hosting, edge).
- Tu veux rester proche de Kubernetes (pods,
play kube,generate kube). - Tu veux éviter un daemon central obligatoire sur la machine.
Reste sur Docker si…
Section intitulée « Reste sur Docker si… »- Ton quotidien dépend d’un Docker Compose complexe et d’intégrations “Docker-only”.
- Tu es dans un contexte où l’écosystème “Docker Desktop + tooling” est imposé.
Si tu veux un guide dédié “migrer sans douleur” : crée/ajoute une page Migration Docker → Podman et pointe-la ici.
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 “terrain”.
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 UIDs/GIDs non privilégiés sur l’hôte :
Hôte Conteneur─────────────────────────────────────────────UID 1000 (vous) UID 0 (root)UID 100000-165535 (subuid) UID 1-65535Les 3 frictions les plus courantes (et les bons réflexes)
Section intitulée « Les 3 frictions les plus courantes (et les bons réflexes) »| Friction | Symptôme | Réflexe |
|---|---|---|
| Ports bas | bind: permission denied sur 80/443 | utiliser ≥1024 ou sysctl |
| Volumes | fichiers “root:root” / permission denied | --userns=keep-id ou options volume |
| Réseau | DNS/perf surprenants | réseau dédié + backend rootless adapté |
Pilier 2 — Réseau : pasta, netavark et DNS
Section intitulée « Pilier 2 — Réseau : pasta, netavark et DNS »En pratique, c’est souvent le réseau rootless qui “surprend”. L’idée : tu veux un réseau où les conteneurs se résolvent par nom et où la perf n’est pas catastrophique.
Exemple : DNS entre conteneurs (rootless)
Section intitulée « Exemple : DNS entre conteneurs (rootless) »# Créer un réseau applicatif (résolution par nom)podman network create app-network
# Exemple simple : un conteneur "db" et un clientpodman run -d --network app-network --name db postgres:16podman run --rm --network app-network alpine ping -c 1 dbPilier 3 — Systemd : Quadlet (recommandé)
Section intitulée « Pilier 3 — Systemd : Quadlet (recommandé) »Si tu veux du “prod-like” sur un serveur Linux, Quadlet est le point d’inflexion : un conteneur devient un service (journalctl, restart, dépendances).
Quadlet est la voie recommandée par l’écosystème Podman, et podman generate systemd est marqué comme déprécié (toujours utilisable, mais plus la direction). ([GitHub][1])
Exemple minimal : nginx.container
Section intitulée « Exemple minimal : nginx.container »[Container]Image=docker.io/library/nginx:alpinePublishPort=8080:80Volume=/data/nginx:/usr/share/nginx/html:ro,Z
[Service]Restart=always
[Install]WantedBy=default.targetActivation (rootless) :
mkdir -p ~/.config/containers/systemd# placer nginx.container dans ce dossier
systemctl --user daemon-reloadsystemctl --user start nginxsystemctl --user enable nginxjournalctl --user -u nginx -fBonus — Pods et intégration Kubernetes (sans cluster)
Section intitulée « Bonus — Pods et intégration Kubernetes (sans cluster) »Quand tu as besoin d’un “mini-Kubernetes mental model” sans déployer un cluster, les pods Podman sont parfaits.
Pod : web + base de données
Section intitulée « Pod : web + base de données »podman pod create --name webapp -p 8080:80
podman run -d --pod webapp --name db \ -e POSTGRES_PASSWORD=secret postgres:16-alpine
podman run -d --pod webapp --name app \ -e DATABASE_URL=postgres://postgres:secret@localhost:5432 \ mon-app:latestpodman play kube : exécuter un manifest sans cluster
Section intitulée « podman play kube : exécuter un manifest sans cluster »podman play kube deployment.yamlpodman play kube deployment.yaml --downpodman generate kube : exporter vers Kubernetes
Section intitulée « podman generate kube : exporter vers Kubernetes »podman generate kube webapp > webapp.yamlmacOS / 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 initpodman machine startpodman run -d -p 8080:80 nginxpodman machine sshParcours recommandé (simple)
Section intitulée « Parcours recommandé (simple) »Choisis ton point d’entrée, puis avance module par module :
| Module | Objectif |
|---|---|
| Installation | être opérationnel (Linux/macOS/Windows) |
| Commandes de base | run/ps/exec/logs/images/nettoyage |
| Run avancé | rootless, namespaces, sécurité, options utiles |
| Volumes | persistance + permissions |
| Réseaux | DNS, ports, isolation, multi-tier |
| Build | Containerfile, multi-stage, optimisation |
Autour de Podman
Section intitulée « Autour de Podman »Évolutions à connaître (pour éviter les surprises)
Section intitulée « Évolutions à connaître (pour éviter les surprises) »Prochaines étapes (3 choix max)
Section intitulée « Prochaines étapes (3 choix max) »FAQ — Questions fréquentes
Section intitulée « FAQ — Questions fréquentes »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
podman run -d nginx # Crée directement le processus
# 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 nginx
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
# Ajouter une BDD (accessible via localhost dans le pod)
podman run -d --pod webapp \
--name db \
-e POSTGRES_PASSWORD=secret \
postgres:16-alpine
# Ajouter l'app (accède à la BDD via localhost:5432)
podman run -d --pod webapp \
--name app \
-e DATABASE_HOST=localhost \
mon-app:latest
Analogie Kubernetes
Le concept de pod Podman est identique à celui de Kubernetes, ce qui facilite la migration.# Générer un manifest Kubernetes depuis un pod Podman
podman generate kube webapp > webapp.yaml
→ Concepts podsÉ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 nginx
curl http://localhost:8080
Ports < 1024 (nécessitent configuration)
# Par défaut, erreur :
podman run -d -p 80:80 nginx
# 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 : Utiliser rootlesskit# Dans ~/.config/containers/containers.conf
[network]
rootless_networking = "pasta"
Tableau récapitulatif
| Port | Mode rootless | Solution |
|---|---|---|
| ≥ 1024 | ✅ Direct | -p 8080:80 |
| < 1024 | ⚠️ Bloqué | sysctl ou rootlesskit |
| 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 alpine:3.21)
# 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 alpine 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 mon-app
Solution 2 : SELinux avec :Z ou :z
# :Z = label privé (un seul conteneur)
podman run -v ~/data:/data:Z mon-app
# :z = label partagé (plusieurs conteneurs)
podman run -v ~/shared:/shared:z mon-app
Solution 3 : keep-id
# Votre UID (1000) devient l'UID 1000 dans le conteneur
podman run --userns=keep-id -v ~/data:/data mon-app
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 nginx
podman generate kube web > web-pod.yaml
# Depuis un pod complet
podman pod create --name webapp -p 8080:80
podman run -d --pod webapp --name db postgres:16
podman run -d --pod webapp --name app mon-app
podman generate kube webapp > webapp.yaml
Exemple de manifest généré
apiVersion: v1
kind: Pod
metadata:
name: webapp
spec:
containers:
- name: db
image: postgres:16
- name: app
image: mon-app:latest
Exécuter un manifest Kubernetes localement
# Déployer un manifest existant
podman play kube webapp.yaml
# Avec un ConfigMap
podman play kube webapp.yaml --configmap config.yaml
# Arrêter et supprimer
podman play kube 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 --memory 512m nginx
# Limite RAM + swap
podman run -d --memory 512m --memory-swap 1g nginx
Limiter le CPU
# Limite à 1.5 cœurs CPU
podman run -d --cpus 1.5 nginx
# Limite en millicores (comme Kubernetes)
podman run -d --cpus 0.5 nginx # 500m
# Limiter à certains cœurs spécifiques
podman run -d --cpuset-cpus 0,1 nginx
Limiter les processus
# Maximum 100 processus
podman run -d --pids-limit 100 nginx
Vérifier les limites
# Statistiques en temps réel
podman stats
# Inspecter les limites configurées
podman inspect nginx --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
# Le réseau 'podman' par défaut n'a pas de DNS
podman run -d --name db postgres:16
podman run --rm alpine ping db
# ping: bad address 'db' ← Échec !
Réseau personnalisé : DNS activé
# 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 postgres:16
podman run --rm --network mon-reseau alpine ping db
# PING db (10.89.0.2): 56 data bytes ← Succès !
Alias DNS
# Ajouter des alias pour un conteneur
podman run -d --network mon-reseau \
--name postgres \
--network-alias db \
--network-alias database \
postgres:16
# 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
Exemple minimal : nginx.container
[Container]
Image=docker.io/library/nginx:alpine
PublishPort=8080:80
Volume=/data/nginx:/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
# Activer au boot
systemctl --user enable nginx.service
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 nginx
# 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 alpine ping other-container
# ping: bad address 'other-container'
Solution : créer un réseau personnalisépodman network create mon-reseau
podman run -d --network mon-reseau --name db postgres
podman run --rm --network mon-reseau alpine ping db # ✅ fonctionne
Problème 3 : Performance réseau
slirp4netns est plus lent que le bridge kernel.Solution : utiliser pasta (Podman 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 play kube 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: nginx:alpine
ports:
- containerPort: 80
hostPort: 8080
# Déployer le manifest
podman play kube webapp.yaml
# Vérifier
podman pod ps
podman ps
curl http://localhost:8080
# Arrêter et supprimer
podman play kube webapp.yaml --down
Ressources Kubernetes supportées
| Ressource | Support |
|---|---|
| Pod | ✅ Complet |
| Deployment | ✅ Complet |
| ConfigMap | ✅ Complet |
| Secret | ✅ Complet |
| Service | ⚠️ Partiel |
| PersistentVolume | ⚠️ Limité |