Durée estimée : 1 heure Niveau : Administrateur Linux intermédiaire Distribution cible : Fedora 39+ / RHEL 9 / Rocky Linux 9
- Concepts fondamentaux
- Prérequis et installation
- Préparer les volumes persistants
- Créer et démarrer le Pod WordPress
- Vérification et accÚs à WordPress
- Démarrage automatique avec systemd (Quadlet)
- Comparaison avec podman-compose
- Aide-mémoire des commandes
Podman est un moteur de conteneurs daemonless (sans dĂ©mon central). LĂ oĂč Docker exige un service dockerd tournant en root en permanence, Podman exĂ©cute chaque conteneur comme un processus enfant direct de l'utilisateur qui l'a lancĂ©.
Docker : shell â dockerd (root) â containerd â runc â conteneur
Podman : shell â podman â conmon â runc â conteneur
Conséquences concrÚtes :
- Pas de single point of failure (pas de daemon)
- Chaque conteneur s'exécute avec les droits de l'utilisateur appelant
- Intégration naturelle avec systemd via le modÚle fork/exec
- Compatible avec les images OCI (mĂȘme format que Docker)
En mode rootfull, le processus Podman (ou le daemon Docker) s'exĂ©cute avec les privilĂšges root sur l'hĂŽte. Le conteneur partage le mĂȘme user namespace que l'hĂŽte : l'UID 0 Ă l'intĂ©rieur du conteneur est l'UID 0 sur l'hĂŽte.
MODE ROOTFULL
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
HĂŽte Conteneur
UID 0 (root) ââââââââș UID 0 (root)
UID 1000 (user) ââââââââș UID 1000
â Processus dans le conteneur = processus root sur l'hĂŽte
â AccĂšs complet au systĂšme de fichiers hĂŽte si mal configurĂ©
â AccĂšs aux appels systĂšme kernel sans filtre supplĂ©mentaire
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
La conséquence directe : si un attaquant exploite une vulnérabilité dans le conteneur et parvient à en sortir (container escape), il obtient un shell root sur l'hÎte. L'isolation repose entiÚrement sur la robustesse du runtime (runc, crun) et du kernel. Un seul maillon faible suffit à compromettre toute la machine.
C'est exactement le modÚle de Docker dans sa configuration par défaut : le daemon dockerd tourne en root, et l'appartenance au groupe docker est techniquement équivalente à avoir sudo sans mot de passe.
Un conteneur rootless s'exécute dans un user namespace dédié. Le noyau Linux crée un espace d'identifiants isolé dans lequel l'UID 0 à l'intérieur du conteneur est mappé sur un UID non-privilégié sur l'hÎte, selon une table de correspondance définie par /etc/subuid et /etc/subgid.
MODE ROOTLESS
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
HĂŽte Conteneur
UID 1001 (monuser) âââââș UID 0 (root apparent)
UID 101001 âââââș UID 1000
UID 101002 âââââș UID 1001
... ...
UID 166536 âââââș UID 65535
â "root" dans le conteneur = utilisateur ordinaire sur l'hĂŽte
â Container escape â attaquant obtient UID 1001 sur l'hĂŽte
â Aucun accĂšs root rĂ©el sur l'hĂŽte
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
Le fichier /etc/subuid définit la plage d'UIDs subordonnés autorisée pour chaque utilisateur :
# Vérifier la configuration subuid/subgid
cat /etc/subuid
# monuser:100000:65536
# âââââââ âââââââ âââââ
# user début taille de la plage
cat /etc/subgid
# monuser:100000:65536
# Vérifier le mapping actif dans un conteneur qui tourne
podman unshare cat /proc/self/uid_map
# 0 1001 1 â UID 0 conteneur = UID 1001 hĂŽte
# 1 100000 65536 â UID 1-65535 conteneur = UID 100000+ hĂŽte| CritĂšre | Rootfull | Rootless |
|---|---|---|
| Processus Podman | S'exécute en root | S'exécute en utilisateur |
| UID 0 dans le conteneur | = root réel sur l'hÎte | = UID non-privilégié sur l'hÎte |
| Container escape | â shell root hĂŽte | â shell utilisateur ordinaire |
| AccĂšs aux ports < 1024 | â Oui (root peut tout) | â Non (voir section limitations) |
AccĂšs aux devices /dev |
â Plus facile | |
| Linux Capabilities | Toutes disponibles | Sous-ensemble uniquement |
| Réseau | netavark (bridge natif) | pasta (default Podman 5+) |
| Surface d'attaque | ĂlevĂ©e | RĂ©duite |
| AdaptĂ© production | Avec prĂ©cautions | â RecommandĂ© (RHEL, ANSSI) |
| Stockage images | /var/lib/containers/ |
~/.local/share/containers/ |
Le user namespace n'est que la premiÚre couche. En mode rootless sur Fedora/RHEL, plusieurs mécanismes s'empilent :
1. SELinux (Mandatory Access Control)
Chaque conteneur reçoit automatiquement un label SELinux container_t. MĂȘme si un processus sort du conteneur, les rĂšgles SELinux limitent ce qu'il peut faire sur l'hĂŽte. Les volumes montĂ©s avec :Z reçoivent un label privĂ© svirt_sandbox_file_t.
On peut le démontrer en live avec un conteneur éphémÚre :
# Lancer un conteneur éphémÚre en arriÚre-plan
podman run -d --rm --name test-selinux docker.io/library/alpine sleep 60
# Inspecter son label SELinux
podman inspect test-selinux --format '{{.ProcessLabel}}'
# system_u:system_r:container_t:s0:c234,c789
# Voir le label depuis le processus lui-mĂȘme cĂŽtĂ© hĂŽte
cat /proc/$(podman inspect test-selinux --format '{{.State.Pid}}')/attr/current
# system_u:system_r:container_t:s0:c234,c789
# Comparer avec le processus shell courant sur l'hĂŽte
cat /proc/self/attr/current
# unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
# Nettoyer
podman stop test-selinuxCe que ça montre :
container_tâ type SELinux dĂ©diĂ© aux conteneurs, distinct de tous les types systĂšmes0:c234,c789â deux catĂ©gories MCS (Multi-Category Security) gĂ©nĂ©rĂ©es alĂ©atoirement Ă chaque dĂ©marrage. Deux conteneurs n'ont jamais les mĂȘmes catĂ©gories, ce qui les empĂȘche d'accĂ©der mutuellement Ă leurs fichiers mĂȘme s'ils tournent sous le mĂȘme UID- La comparaison avec
unconfined_tsur l'hĂŽte rend la diffĂ©rence immĂ©diatement visible : mĂȘme si un attaquant sort du conteneur, SELinux refuse toute action hors du domainecontainer_tavant mĂȘme que les permissions Unix entrent en jeu
2. Seccomp (Secure Computing Mode)
Podman applique par dĂ©faut un profil seccomp qui filtre les appels systĂšme dangereux (ex. ptrace, mount, reboot). Un conteneur rootless ne peut pas appeler ces syscalls mĂȘme s'il croit ĂȘtre root.
3. Linux Capabilities
En rootless, les capabilities disponibles dans le conteneur sont un sous-ensemble trÚs restreint. CAP_SYS_ADMIN, CAP_NET_ADMIN, CAP_SYS_PTRACE sont notamment absentes par défaut.
# Vérifier les capabilities effectives dans un conteneur rootless
podman run --rm docker.io/library/alpine cat /proc/self/status | grep Cap
# CapEff: 00000000800405fb â sous-ensemble limitĂ©En mode rootless, un utilisateur non-privilĂ©giĂ© ne peut pas crĂ©er d'interfaces rĂ©seau rĂ©elles sur l'hĂŽte. Podman utilise donc un backend rĂ©seau en espace utilisateur.
Depuis Podman 5.0 (et RHEL 9.5), le backend par défaut est pasta (Pack A Subtle Tap Abstraction), successeur de slirp4netns. Pasta crée un namespace réseau isolé et fait transiter le trafic sans toucher aux interfaces réseau de l'hÎte.
RĂSEAU ROOTLESS (pasta)
âââââââââââââââââââââââââââââââââââââââââââââââââ
HĂŽte (eth0: 192.168.1.10)
â
â pasta (espace utilisateur)
â âââ copie l'adresse IP de l'hĂŽte dans le namespace
â âââ redirige le trafic sans NAT
â
Namespace réseau du conteneur
âââ interface rĂ©seau virtuelle (mĂȘme IP que l'hĂŽte)
âââââââââââââââââââââââââââââââââââââââââââââââââ
RĂSEAU ROOTFULL (netavark)
âââââââââââââââââââââââââââââââââââââââââââââââââ
HĂŽte (eth0: 192.168.1.10)
â
â netavark (bridge kernel)
â
Interface bridge podman0 (10.88.0.1/16)
âââ veth â conteneur (10.88.0.2)
âââââââââââââââââââââââââââââââââââââââââââââââââ
Le noyau Linux interdit aux processus sans CAP_NET_BIND_SERVICE d'écouter sur les ports inférieurs à 1024 (ports dits "privilégiés" : 80, 443, 22...). En rootless, cette restriction s'applique.
C'est pour cette raison que nous utilisons le port 8088 dans ce tutoriel : aucune configuration supplémentaire n'est nécessaire.
# Vérifier le seuil des ports non-privilégiés sur votre systÚme
cat /proc/sys/net/ipv4/ip_unprivileged_port_start
# 1024 (valeur par défaut)
# Si vous avez besoin d'exposer le port 443 en rootless (ex. avec un reverse proxy),
# il est possible d'abaisser ce seuil (modification system-wide, impacte tous les utilisateurs) :
# /etc/sysctl.d/99-podman-rootless.conf
# net.ipv4.ip_unprivileged_port_start=443
# sudo sysctl --system
# Solution recommandée en production : utiliser un reverse proxy (Nginx, Traefik, Caddy)
# en amont qui écoute sur 80/443 et proxy vers votre port non-privilégié.Le mode rootless est un principe universel de sécurité, applicable à toute mise en production quel que soit le runtime. Red Hat le recommande pour tous les déploiements Podman sur RHEL 9. Cette approche s'aligne avec les principes de défense en profondeur préconisés par l'ANSSI dans ses guides de sécurisation des conteneurs :
- Principe du moindre privilÚge : aucun processus conteneur ne détient de privilÚges root réels sur l'hÎte
- Réduction de la surface d'attaque : un container escape ne compromet que le compte utilisateur, pas la machine
- Isolation multi-couches : user namespaces + SELinux + seccomp + capabilities réduites
- Auditabilité : les processus conteneurs apparaissent avec l'UID de l'utilisateur dans les logs systÚme (
journalctl,ps,auditd), ce qui facilite la traçabilité
RĂšgle Ă appliquer pour toute mise en production : rootless par dĂ©faut, rootfull uniquement si une contrainte technique l'impose â et dans ce cas, documentĂ©e et justifiĂ©e.
Docker supporte lui aussi le mode rootless depuis la version 20.10 (décembre 2020). Le mécanisme est identique : le daemon dockerd et les conteneurs s'exécutent dans un user namespace, l'UID 0 du conteneur est mappé sur un UID non-privilégié de l'hÎte via /etc/subuid.
# Activation du mode rootless Docker (à faire manuellement, non activé par défaut)
dockerd-rootless-setuptool.sh install
# Démarrage du daemon rootless via systemd utilisateur
systemctl --user enable --now docker.service
sudo loginctl enable-linger $USER
# Vérification
docker info | grep rootless
# rootless: trueCependant, Docker rootless présente des différences structurelles importantes avec Podman rootless :
| CritĂšre | Podman rootless | Docker rootless |
|---|---|---|
| ActivĂ© par dĂ©faut | â Oui | â Non, opt-in manuel |
| Daemon persistent | â Aucun daemon | â
dockerd en mode user |
| Single point of failure | â Aucun | |
| Socket exposé | Aucun par défaut | $XDG_RUNTIME_DIR/docker.sock |
| Docker Swarm | N/A | â Non supportĂ© en rootless |
| IntĂ©gration systemd | â Native (Quadlet) | |
| SELinux | â Natif RHEL/Fedora |
Le point critique : en Docker rootfull (configuration par dĂ©faut de la majoritĂ© des dĂ©ploiements), le socket /var/run/docker.sock expose une surface d'attaque root permanente. Avoir accĂšs Ă ce socket â que ce soit via un conteneur mal configurĂ© ou un utilisateur du groupe docker â Ă©quivaut Ă disposer d'un accĂšs root total sur la machine hĂŽte, sans mot de passe sudo.
# Démonstration du risque Docker rootfull :
# Un conteneur avec accĂšs au socket peut monter le systĂšme de fichiers hĂŽte en root
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
docker sh -c "docker run --rm -v /:/host alpine chroot /host id"
# uid=0(root) gid=0(root) â root sur l'hĂŽte, depuis un conteneur
â ïž Cette commande est fournie Ă titre pĂ©dagogique uniquement pour illustrer le risque. Ne jamais monter le socket Docker dans un conteneur en production.
Un Pod Podman s'inspire directement des Pods Kubernetes. C'est un groupe de conteneurs qui partagent :
- Le mĂȘme namespace rĂ©seau (ils se voient via
localhost) - Le mĂȘme espace IPC (Inter-Process Communication)
- Optionnellement le mĂȘme espace PID
Chaque pod contient automatiquement un conteneur infra (aussi appelé pause container). Ce conteneur léger existe uniquement pour maintenir les namespaces actifs pendant toute la durée de vie du pod. Les autres conteneurs rejoignent ces namespaces partagés.
ââââââââââââââââââââââââ POD wp-blog ââââââââââââââââââââââââ
â ââââââââââââââââ ââââââââââââââââ ââââââââââââââââ â
â â infra â â mariadb â â wordpress â â
â â (pause) â â â â â â
â ââââââââââââââââ ââââââââââââââââ ââââââââââââââââ â
â â â â â
â âââââââââ rĂ©seau partagĂ© (localhost) ââ â
â port 8088 exposĂ© vers l'hĂŽte â
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
L'avantage : WordPress parle à MariaDB via 127.0.0.1 au lieu d'un nom de réseau. Pas besoin de réseau inter-conteneurs complexe.
Deux approches pour la persistance :
| CritÚre | Volume nommé (podman volume) |
Bind mount (-v /chemin/hĂŽte:/chemin/ctr) |
|---|---|---|
| Gestion | Podman gÚre le répertoire | Vous gérez le répertoire |
| Localisation | ~/.local/share/containers/storage/volumes/ |
OĂč vous voulez |
| Droits rootless | Podman ajuste automatiquement | Ă configurer manuellement |
| SELinux | Géré automatiquement | Nécessite :Z ou :z |
| Backup | podman volume export |
tar classique |
Dans ce tutoriel, nous utilisons des volumes nommĂ©s : Podman gĂšre lui-mĂȘme les droits dans le user namespace, ce qui Ă©vite les problĂšmes de permissions en mode rootless.
L'option --rm supprime automatiquement le conteneur lorsqu'il s'arrĂȘte. Cela s'applique ici au niveau de la commande podman run, mais attention : avec des pods, on prĂ©fĂ©rera la recrĂ©ation propre via systemd.
Pour les pods, l'équivalent est podman pod rm aprÚs podman pod stop.
Sur Fedora/RHEL 9/Rocky 9, Podman est disponible dans les dépÎts officiels via le méta-paquet container-tools.
# Installer les outils conteneurs
sudo dnf install -y podman podman-compose buildah skopeo
# Vérifier la version (>= 4.4 requis pour Quadlet)
podman --version
# Exemple : podman version 4.9.4
# Vérifier que cgroups v2 est actif (requis pour rootless)
cat /sys/fs/cgroup/cgroup.controllers
# Doit afficher : cpuset cpu io memory hugetlb pids rdma misc# Vérifier les subuid/subgid (doit exister pour votre utilisateur)
grep $USER /etc/subuid /etc/subgid
# Si absent, configurer :
sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 $USER
# Vérifier l'état de Podman en mode utilisateur
podman info --format '{{.Host.Security}}'
# rootless: true doit apparaßtrePourquoi créer les volumes manuellement ? Podman crée automatiquement un volume nommé au premier
podman runqui le rĂ©fĂ©rence âpodman volume createn'est pas obligatoire. On le fait ici explicitement pour rendre l'intention visible, pouvoir inspecter le chemin physique et le label SELinux avant tout lancement, et dĂ©tecter une faute de frappe tĂŽt plutĂŽt que de laisser Podman crĂ©er silencieusement un volume avec le mauvais nom. En environnement automatisĂ© ou avec Quadlet, laisser Podman crĂ©er les volumes Ă la volĂ©e est tout Ă fait acceptable.
# Créer les deux volumes qui persisteront les données
podman volume create wp_data
podman volume create mysql_data
# Vérifier leur création et localisation
podman volume ls
podman volume inspect wp_data
# Affiche le chemin rĂ©el : ~/.local/share/containers/storage/volumes/wp_data/_data/mysql_data: contient les fichiers de base de donnĂ©es MySQL (/var/lib/mysql). Persiste toutes les donnĂ©es du site mĂȘme si les conteneurs sont supprimĂ©s.wp_data: contient les fichiers WordPress (/var/www/html), y compris les thĂšmes, plugins et uploads.
Sur Fedora/RHEL, SELinux est actif par défaut. Les volumes nommés Podman reçoivent automatiquement le contexte container_file_t. Vous pouvez le vérifier :
# Inspecter le chemin physique du volume
MYSQL_VOL_PATH=$(podman volume inspect mysql_data --format '{{.Mountpoint}}')
ls -laZ "$MYSQL_VOL_PATH"
# Doit afficher : system_u:object_r:container_file_t:s0Avec les volumes nommĂ©s, aucune manipulation semanage manuelle n'est nĂ©cessaire â Podman s'en charge.
Nous allons procéder en trois étapes :
- Créer le pod avec la configuration réseau
- Lancer MariaDB dans le pod
- Lancer WordPress dans le pod
podman pod create \
--name wp-blog \
--publish 8088:80Explication des options :
--name wp-blog: nomme le pod pour y faire référence facilement--publish 8088:80: expose le port 80 du pod sur le port 8088 de l'hÎte (port non-standard pour éviter les conflits). Important : les mappings de ports se définissent au niveau du pod, pas des conteneurs.
# Vérifier la création du pod
podman pod ps
# POD ID NAME STATUS CREATED INFRA ID # OF CONTAINERS
# abc123... wp-blog Created 1s ago def456... 1Le pod contient déjà 1 conteneur : le conteneur infra (pause).
podman run -d \
--pod wp-blog \
--name wp-mysql \
--rm \
-e MYSQL_ROOT_PASSWORD=R00tS3cur3Pass! \
-e MYSQL_DATABASE=wordpress \
-e MYSQL_USER=wpuser \
-e MYSQL_PASSWORD=WpS3cur3Pass! \
-v mysql_data:/var/lib/mysql \
docker.io/library/mariadb:11Explication des options :
--pod wp-blog: attache ce conteneur au pod (il rejoint le namespace rĂ©seau partagĂ©)--rm: le conteneur sera supprimĂ© automatiquement Ă l'arrĂȘt-e: variables d'environnement passĂ©es au conteneur (configuration MariaDB)-v mysql_data:/var/lib/mysql: monte le volume nommĂ© sur le rĂ©pertoire des donnĂ©es MySQLdocker.io/library/mariadb:11: image officielle MariaDB, version fixĂ©e (Ă©vite les surprises en production)
â ïž SĂ©curitĂ© : Dans un contexte de production, utilisezpodman secretpour gĂ©rer les mots de passe. Ici on simplifie pour la pĂ©dagogie.
# Attendre que MariaDB soit prĂȘt (environ 30 secondes)
podman logs -f wp-mysql
# Chercher : "ready for connections" dans les logs
# Ctrl+C pour quitter les logspodman run -d \
--pod wp-blog \
--name wp-app \
--rm \
-e WORDPRESS_DB_HOST=127.0.0.1:3306 \
-e WORDPRESS_DB_USER=wpuser \
-e WORDPRESS_DB_PASSWORD=WpS3cur3Pass! \
-e WORDPRESS_DB_NAME=wordpress \
-v wp_data:/var/www/html \
docker.io/library/wordpress:latestPoint clĂ© â WORDPRESS_DB_HOST="127.0.0.1:3306" : puisque WordPress et MariaDB partagent le mĂȘme namespace rĂ©seau dans le pod, MariaDB est accessible sur 127.0.0.1 (localhost du pod). C'est une diffĂ©rence fondamentale avec Docker Compose oĂč les services communiquent via leur nom de service sur un rĂ©seau virtuel dĂ©diĂ©.
# Vue d'ensemble du pod
podman pod ps
# POD ID NAME STATUS CREATED INFRA ID # OF CONTAINERS
# abc123... wp-blog Running 2m ago def456... 3
# Détail des conteneurs dans le pod
podman ps --pod
# CONTAINER ID IMAGE COMMAND STATUS NAMES POD ID
# xxx... localhost/podman-pause:latest Up 2 min wp-blog-infra abc123
# yyy... docker.io/library/mariadb:11 mariadbd Up 90 sec wp-mysql abc123
# zzz... docker.io/library/wordpress... apache2-foreground Up 60 sec wp-app abc123
# Consulter les logs d'un conteneur spécifique
podman logs wp-mysql
podman logs wp-appOuvrir un navigateur et accéder à :
http://localhost:8088
Ou depuis une machine distante :
http://<IP_DU_SERVEUR>:8088
Si vous ĂȘtes derriĂšre un pare-feu (firewalld sur RHEL/Fedora) :
sudo firewall-cmd --add-port=8088/tcp --permanent sudo firewall-cmd --reload
L'installeur WordPress doit apparaĂźtre. Renseignez :
- Titre du site
- Nom d'utilisateur administrateur
- Mot de passe admin
# Stopper le pod (les conteneurs avec --rm sont supprimés automatiquement)
podman pod rm -f wp-blog
# Vérifier que les conteneurs ont été supprimés (--rm)
podman ps -a
# wp-mysql et wp-app ne doivent plus apparaĂźtre
# Le conteneur infra du pod lui existe toujours
# VĂ©rifier que les volumes sont toujours lĂ
podman volume ls
# local mysql_data â donnĂ©es prĂ©servĂ©es
# local wp_data â fichiers WordPress prĂ©servĂ©sL'effet de --rm expliquĂ©
--rm demande Ă Podman de supprimer le conteneur dĂšs qu'il s'arrĂȘte. Ce qui est supprimĂ© : le conteneur lui-mĂȘme (son systĂšme de fichiers en couches, ses mĂ©tadonnĂ©es, son ID). Ce qui n'est pas supprimĂ© : les volumes nommĂ©s â ils ont un cycle de vie indĂ©pendant des conteneurs qui les utilisent.
C'est la distinction fondamentale entre deux couches :
Couche conteneur â Ă©phĂ©mĂšre â supprimĂ©e avec --rm
Couche volume â persistante â survit Ă l'arrĂȘt et Ă la suppression du conteneur
RecrĂ©er les conteneurs est rapide car les images sont dĂ©jĂ prĂ©sentes localement â Podman ne les re-tĂ©lĂ©charge pas. Seul le conteneur (la couche d'exĂ©cution) est recréé, les donnĂ©es dans les volumes sont intactes.
podman pod rm -f wp-blog
# RecrĂ©er le conteneur MariaDB â dĂ©marrage quasi-instantanĂ© (image en cache)
podman pod create \
--name wp-blog \
--publish 8088:80
podman run -d \
--pod wp-blog \
--name wp-mysql \
--rm \
-e MYSQL_ROOT_PASSWORD=R00tS3cur3Pass! \
-e MYSQL_DATABASE=wordpress \
-e MYSQL_USER=wpuser \
-e MYSQL_PASSWORD=WpS3cur3Pass! \
-v mysql_data:/var/lib/mysql \
docker.io/library/mariadb:11
# MariaDB démarre plus vite au second lancement :
# les fichiers de base de données existent déjà dans le volume,
# il n'y a pas d'initialisation Ă faire â juste un dĂ©marrage normal
podman logs -f wp-mysql
# Chercher : "ready for connections" â apparaĂźt bien plus vite qu'au premier lancement
# Recréer le conteneur WordPress
podman run -d \
--pod wp-blog \
--name wp-app \
--rm \
-e WORDPRESS_DB_HOST=127.0.0.1:3306 \
-e WORDPRESS_DB_USER=wpuser \
-e WORDPRESS_DB_PASSWORD=WpS3cur3Pass! \
-e WORDPRESS_DB_NAME=wordpress \
-v wp_data:/var/www/html \
docker.io/library/wordpress:latest
# Vérifier que les deux conteneurs sont de nouveau actifs
podman ps --podAccĂ©der Ă http://localhost:8088 â WordPress s'affiche directement avec le site dĂ©jĂ configurĂ©, sans passer par l'installeur. La preuve que les donnĂ©es ont survĂ©cu.
Note sur
--rmet les pods :--rmsignifie que le conteneur est effacĂ© Ă l'arrĂȘt. Les volumes eux ne sont pas supprimĂ©s. C'est la distinction essentielle entre la couche conteneur (Ă©phĂ©mĂšre) et la couche donnĂ©es (persistante). C'est pourquoi dans la section suivante, nous passerons Ă Quadlet qui gĂšre le cycle de vie complet de maniĂšre dĂ©clarative.
# ArrĂȘter et supprimer le pod et tous ses conteneurs
podman pod rm -f wp-blog
# Supprimer les volumes si on veut repartir de zéro (DESTRUCTIF)
podman volume rm mysql_data wp_datapodman generate systemd est déprécié depuis Podman 4.4. La documentation officielle recommande d'utiliser Quadlet pour toute gestion de conteneurs sous systemd.
Quadlet est un générateur systemd intégré à Podman depuis la version 4.4. Il lit des fichiers déclaratifs (.container, .pod, .volume, .network) placés dans ~/.config/containers/systemd/ et génÚre automatiquement des units systemd au démarrage du systÚme, via le mécanisme des systemd generators.
Fichiers Quadlet systemd generator Units systemd
~/.config/containers/ â /usr/libexec/podman/ â services gĂ©rables
systemd/*.container quadlet par systemctl
Les avantages par rapport Ă generate systemd :
- Déclaratif : on décrit l'état souhaité, pas les commandes
- Pas de dérive : modifier le fichier Quadlet et faire
daemon-reloadsuffit - Evolutions automatiques : quand Podman ajoute des fonctionnalités, les units générées en profitent sans regénération manuelle
- Géré par Red Hat comme approche officielle dans RHEL 9+
Par dĂ©faut, systemd dĂ©truit la session utilisateur (et tous ses services) quand l'utilisateur se dĂ©connecte. Le user lingering demande Ă systemd de maintenir la session utilisateur active mĂȘme sans connexion ouverte, et de la dĂ©marrer automatiquement au boot.
# Activer le lingering pour votre utilisateur (commande à exécuter en tant que root)
sudo loginctl enable-linger $USER
# Vérifier
loginctl show-user $USER | grep Linger
# Linger=yesSans cette Ă©tape, vos conteneurs s'arrĂȘteraient Ă la dĂ©connexion SSH.
Nous allons créer 4 fichiers Quadlet :
~/.config/containers/systemd/
âââ wp-blog.pod # DĂ©finition du pod
âââ wp-volumes.volume # Volume mysql_data (optionnel, dĂ©claratif)
âââ wp-mysql.container # Conteneur MySQL
âââ wp-app.container # Conteneur WordPress
mkdir -p ~/.config/containers/systemdPour aller plus loin : l'outil
podletgĂ©nĂšre automatiquement des fichiers Quadlet depuis une commandepodman runou undocker-compose.yml. Utile une fois les concepts maĂźtrisĂ©s â Ă explorer aprĂšs la masterclass. Documentation et sources : https://github.com/containers/podlet
Par exemple, la commande MariaDB vue en section 4.2 peut ĂȘtre convertie en fichier Quadlet en une seule ligne â il suffit de prĂ©fixer podlet devant podman :
# Installation via cargo (Rust requis)
cargo install podlet
# create the directory
mkdir ~/.config/containers/systemd/
# Générer le fichier wp-mysql.container depuis la commande podman run
~/.cargo/bin/podlet --unit-directory podman run -d \ 1 â”
--pod wp-blog \
--name wp-mysql \
-e MYSQL_ROOT_PASSWORD=R00tS3cur3Pass! \
-e MYSQL_DATABASE=wordpress \
-e MYSQL_USER=wpuser \
-e MYSQL_PASSWORD=WpS3cur3Pass! \
-v mysql_data:/var/lib/mysql \
docker.io/library/mariadb:11
# Résultat écrit automatiquement dans :
# ~/.config/containers/systemd/wp-mysql.container
# Le fichier généré est à relire et compléter (After=, Requires=, etc.)
# avant de l'activer avec systemctl --user daemon-reloadCe fichier déclare le pod et son exposition réseau.
cat > ~/.config/containers/systemd/wp-blog.pod << 'EOF'
[Unit]
Description=Pod WordPress Blog
[Pod]
PodName=wp-blog
PublishPort=8088:80
[Install]
WantedBy=default.target
EOFExplication des sections :
[Unit]: section systemd standard (description, dépendances)[Pod]: section spécifique Quadlet pour la configuration du podPublishPort: équivalent du--publishdepodman pod create[Install]avecWantedBy=default.target: active le service au boot. Pour les conteneurs rootless, on utilisedefault.targetet nonmulti-user.target(qui ne fonctionne pas en mode user).
cat > ~/.config/containers/systemd/wp-mysql.container << 'EOF'
[Unit]
Description=MariaDB pour WordPress
After=wp-blog-pod.service
Requires=wp-blog-pod.service
[Container]
Image=docker.io/library/mariadb:11
ContainerName=wp-mysql
Pod=wp-blog.pod
Environment=MYSQL_ROOT_PASSWORD=R00tS3cur3Pass!
Environment=MYSQL_DATABASE=wordpress
Environment=MYSQL_USER=wpuser
Environment=MYSQL_PASSWORD=WpS3cur3Pass!
Volume=mysql_data:/var/lib/mysql
[Service]
Restart=always
TimeoutStartSec=120
[Install]
WantedBy=default.target
EOFExplication :
After=wp-blog-pod.service/Requires=wp-blog-pod.service: MariaDB ne démarre qu'aprÚs que le pod infra soit actif. Quadlet génÚre un servicewp-blog-pod.servicepour le pod.Pod=wp-blog.pod: lie ce conteneur au pod déclaré danswp-blog.podTimeoutStartSec=120: MariaDB peut mettre du temps à s'initialiser (écriture des fichiers DB au premier lancement), on lui donne 2 minutes
cat > ~/.config/containers/systemd/wp-app.container << 'EOF'
[Unit]
Description=WordPress Application
After=wp-mysql.service
Requires=wp-mysql.service
[Container]
Image=docker.io/library/wordpress:latest
ContainerName=wp-app
Pod=wp-blog.pod
Environment=WORDPRESS_DB_HOST=127.0.0.1:3306
Environment=WORDPRESS_DB_USER=wpuser
Environment=WORDPRESS_DB_PASSWORD=WpS3cur3Pass!
Environment=WORDPRESS_DB_NAME=wordpress
Volume=wp_data:/var/www/html
AutoUpdate=registry
[Service]
Restart=always
TimeoutStartSec=120
[Install]
WantedBy=default.target
EOFPoint notable â AutoUpdate=registry : Podman peut vĂ©rifier automatiquement si une nouvelle image est disponible dans le registry. Avec podman auto-update, il mettra Ă jour les conteneurs ayant ce label. C'est une fonctionnalitĂ© unique Ă Podman, sans Ă©quivalent natif dans Docker (qui nĂ©cessite des outils tiers comme Watchtower).
# Recharger systemd pour qu'il lise les nouveaux fichiers Quadlet
systemctl --user daemon-reload
# Vérifier que les units ont bien été générés
systemctl --user list-unit-files | grep wp
# wp-blog-pod.service generated
# wp-mysql.service generated
# wp-app.service generated
# Démarrer le pod (les conteneurs démarrent en cascade)
systemctl --user start wp-blog-pod.service
# Vérifier le statut
systemctl --user status wp-blog-pod.service
systemctl --user status wp-mysql.service
systemctl --user status wp-app.service
# Consulter les logs
journalctl --user -u wp-mysql.service -f
journalctl --user -u wp-app.service -fhttp://localhost:8088
Le service démarrera automatiquement à chaque boot de la machine, sans aucune connexion nécessaire (grùce au lingering).
# Simuler un reboot (sans redémarrer réellement)
systemctl --user stop wp-app.service wp-mysql.service wp-blog-pod.service
# Attendre quelques secondes, puis vérifier
sleep 5
systemctl --user status wp-blog-pod.service
# Si WantedBy=default.target est bien configuré, il devrait redémarrer
# Pour confirmer le comportement au vrai boot
sudo reboot
# AprÚs redémarrage, se reconnecter et vérifier
systemctl --user status wp-blog-pod.servicepodman-compose est une implémentation Python de Docker Compose utilisant Podman comme backend. Il lit les fichiers docker-compose.yml standards et les exécute avec Podman, offrant une compatibilité quasi-totale sans modifier les fichiers Compose.
# Installation (Fedora/RHEL)
sudo dnf install -y podman-compose
# Ou via pip
pip3 install --user podman-composeLe dépÎt officiel containers/podman-compose sur GitHub inclut un exemple WordPress :
https://github.com/containers/podman-compose/blob/main/examples/wordpress/docker-compose.yaml
Voici un fichier docker-compose.yml adapté de cet exemple, avec MariaDB et le port 8088.
PlutĂŽt que de coller le contenu Ă la main dans nano, on le crĂ©e directement avec un heredoc â cela Ă©vite tout problĂšme de guillemets ou d'indentation au copier-coller :
cat > ~/wordpress-compose/docker-compose.yml << 'EOF'
version: "3.8"
services:
db:
image: docker.io/library/mariadb:11
restart: always
environment:
MYSQL_ROOT_PASSWORD: R00tS3cur3Pass!
MYSQL_DATABASE: wordpress
MYSQL_USER: wpuser
MYSQL_PASSWORD: WpS3cur3Pass!
volumes:
- mysql_data:/var/lib/mysql
wordpress:
image: docker.io/library/wordpress:latest
restart: always
depends_on:
- db
ports:
- "8088:80"
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_USER: wpuser
WORDPRESS_DB_PASSWORD: WpS3cur3Pass!
WORDPRESS_DB_NAME: wordpress
volumes:
- wp_data:/var/www/html
volumes:
mysql_data:
wp_data:
EOF
# Vérifier le résultat avant de lancer
cat ~/wordpress-compose/docker-compose.ymlDifférence notable avec le pod : dans Docker Compose / podman-compose, les services communiquent via leur nom de service (
db:3306) sur un rĂ©seau virtuel dĂ©diĂ© créé automatiquement. Dans le pod Podman, ils utilisaient127.0.0.1car ils partagent le mĂȘme namespace rĂ©seau.
podman-compose et les pods :
podman-composecrĂ©e automatiquement un pod en coulisse, nommĂ© d'aprĂšs le rĂ©pertoire de travail. Tu peux le constater avecpodman pod psaprĂšs unpodman-compose up. Ce pod est gĂ©rĂ© de façon transparente â tu ne le dĂ©finis pas, tu ne le configures pas. C'est pourquoi la communication inter-conteneurs passe par le nom de service (db:3306) sur un rĂ©seau virtuel dĂ©diĂ©, et non par127.0.0.1comme dans un pod Podman natif oĂč les conteneurs partagent le mĂȘme namespace rĂ©seau. En rĂ©sumĂ© : podman-compose utilise les pods comme mĂ©canisme interne, mais t'en abstrait complĂštement.
# Créer le répertoire de travail et générer le fichier (fait en section 7.2)
mkdir -p ~/wordpress-compose
cd ~/wordpress-compose
# Lancer les conteneurs en arriĂšre-plan
podman-compose up -d
# Vérifier les conteneurs
podman ps
# Accéder à WordPress
# http://localhost:8088
# Stopper et supprimer les conteneurs
podman-compose down
# Supprimer également les volumes (DESTRUCTIF)
podman-compose down --volumesVoici comment créer un service systemd pour lancer podman-compose au démarrage :
# Créer le fichier de service utilisateur
mkdir -p ~/.config/systemd/user
cat > ~/.config/systemd/user/wordpress-compose.service << 'EOF'
[Unit]
Description=WordPress via podman-compose
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=%h/wordpress-compose
ExecStart=/usr/bin/podman-compose up -d
ExecStop=/usr/bin/podman-compose down
Restart=on-failure
[Install]
WantedBy=default.target
EOF
# Activer le service
systemctl --user daemon-reload
systemctl --user enable --now wordpress-compose.service
# Vérifier
systemctl --user status wordpress-compose.serviceExplication des directives clés :
Type=oneshot: la commande se termine rapidement (lance les conteneurs en arriĂšre-plan avec-d), ce qui est normalRemainAfterExit=yes: le service reste "actif" mĂȘme aprĂšs la fin deExecStart, ce qui est correct pour un service de type oneshotWorkingDirectory=%h/wordpress-compose:%hest le spĂ©cificateur systemd pour le rĂ©pertoire home de l'utilisateur
| CritÚre | Pod + CLI Podman | podman-compose | Quadlet (recommandé) |
|---|---|---|---|
| Fichier de config | Commandes shell | docker-compose.yml |
Fichiers .container/.pod |
| CompatibilitĂ© Docker | Non | â Oui | Non |
| IntĂ©gration systemd | Manuelle | Via unit custom | â Native |
| Migration Docker | Réécriture | â Transparente | Réécriture |
| RecommandĂ© par Red Hat | â PĂ©dagogique | DĂ©pend du projet | â Production |
| Auto-update images | Manuel | Manuel | â
AutoUpdate=registry |
| Gestion des secrets | Variables env | Variables env | â
podman secret |
| Statut Red Hat | Stable | CommunautĂ© | â Officiel RHEL 9+ |
Utilisez le Pod Podman + Quadlet quand :
- Vous déployez en production sur un serveur Linux (RHEL, Rocky, Fedora Server)
- Vous voulez une intégration systemd native et des auto-updates
- Vous partez d'un nouveau projet (pas de Compose existant)
Utilisez podman-compose quand :
- Vous migrez un projet existant depuis Docker sans modifier les fichiers Compose
- Vous travaillez en environnement de développement
- L'équipe est habituée à la syntaxe Docker Compose
# Créer
podman pod create --name wp-blog --publish 8088:80
# DĂ©marrer / ArrĂȘter
podman pod start wp-blog
podman pod stop wp-blog
# Supprimer (arrĂȘte et supprime tous les conteneurs du pod)
podman pod rm -f wp-blog
# Lister les pods
podman pod ps
# Inspecter un pod
podman pod inspect wp-blog# Lister les conteneurs (avec leur pod)
podman ps --pod
# Logs d'un conteneur
podman logs -f wp-mysql
podman logs -f wp-app
# Exécuter une commande dans un conteneur
podman exec -it wp-mysql mariadb -u wpuser -p wordpress
# Statistiques en temps réel
podman stats# Lister les volumes
podman volume ls
# Inspecter
podman volume inspect mysql_data
# Exporter un volume (backup)
podman volume export mysql_data -o mysql_backup.tar
# Importer un volume (restauration)
podman volume import mysql_data mysql_backup.tar
# Supprimer les volumes orphelins
podman volume prune# Recharger aprĂšs modification des fichiers Quadlet
systemctl --user daemon-reload
# DĂ©marrer / ArrĂȘter / RedĂ©marrer
systemctl --user start wp-blog-pod.service
systemctl --user stop wp-blog-pod.service
systemctl --user restart wp-app.service
# Statut et logs
systemctl --user status wp-app.service
journalctl --user -u wp-app.service -f
# Activer le lingering
sudo loginctl enable-linger $USER
loginctl show-user $USER | grep Linger# Démarrer
podman-compose up -d
# ArrĂȘter
podman-compose down
# Voir les logs
podman-compose logs -f
# Voir le statut
podman-compose ps
# Auto-update des images
podman auto-updateVérifier les subuid/subgid :
grep <user> /etc/subuid /etc/subgidSi absent :
sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 <user>sudo loginctl enable-linger <user>
ls /var/lib/systemd/linger/sudo mkdir -p /run/user/<uid>
sudo chown <user>:<user> /run/user/<uid>
sudo chmod 700 /run/user/<uid>sudo systemctl edit user@<uid>.service[Service]
Environment=XDG_RUNTIME_DIR=/run/user/<uid>cat >> ~/.bashrc << 'EOF'
export XDG_RUNTIME_DIR=/run/user/$(id -u)
export DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u)/bus
EOF
source ~/.bashrcsudo systemctl daemon-reload
sudo systemctl start user@<uid>.service
sudo systemctl enable user@<uid>.service
sudo systemctl status user@<uid>.serviceRéférences officielles
- Documentation Podman : https://docs.podman.io
- Quadlet (podman-systemd.unit) : https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html
- podman-compose : https://github.com/containers/podman-compose
- ANSSI â Recommandations de sĂ©curitĂ© relatives au dĂ©ploiement de conteneurs Docker : https://www.ssi.gouv.fr/guide/recommandations-de-securite-relatives-au-deploiement-de-conteneurs-docker/