Skip to content

Instantly share code, notes, and snippets.

@stephdl
Last active March 17, 2026 15:43
Show Gist options
  • Select an option

  • Save stephdl/8ef09c0290a6baf3a3a45873a5f1c469 to your computer and use it in GitHub Desktop.

Select an option

Save stephdl/8ef09c0290a6baf3a3a45873a5f1c469 to your computer and use it in GitHub Desktop.
🐳 Masterclass Podman — WordPress Rootless sur Fedora / RHEL / Rocky Linux

🐳 Masterclass Podman — WordPress Rootless sur Fedora / RHEL / Rocky Linux

Durée estimée : 1 heure Niveau : Administrateur Linux intermédiaire Distribution cible : Fedora 39+ / RHEL 9 / Rocky Linux 9


Sommaire

  1. Concepts fondamentaux
  2. Prérequis et installation
  3. Préparer les volumes persistants
  4. Créer et démarrer le Pod WordPress
  5. Vérification et accÚs à WordPress
  6. Démarrage automatique avec systemd (Quadlet)
  7. Comparaison avec podman-compose
  8. Aide-mémoire des commandes

1. Concepts fondamentaux

1.1 Podman vs Docker — les diffĂ©rences clĂ©s

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)

1.2 Conteneurs Rootless vs Rootfull — concepts et sĂ©curitĂ©

Le mode Rootfull — comment ça marche et pourquoi c'est risquĂ©

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.

Le mode Rootless — l'isolation par les user namespaces

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

Tableau comparatif complet

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 ⚠ Restreint
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/

Les mécanismes de sécurité additionnels en rootless

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-selinux

Ce que ça montre :

  • container_t — type SELinux dĂ©diĂ© aux conteneurs, distinct de tous les types systĂšme
  • s0: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_t sur l'hĂŽte rend la diffĂ©rence immĂ©diatement visible : mĂȘme si un attaquant sort du conteneur, SELinux refuse toute action hors du domaine container_t avant 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Ă©

Le rĂ©seau en rootless — pasta et ses implications

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)
─────────────────────────────────────────────────

Limitation clé : les ports privilégiés (< 1024)

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é.

Pourquoi rootless est la recommandation officielle Red Hat / ANSSI

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 rootless — possible, mais non activĂ© par dĂ©faut

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: true

Cependant, 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 ⚠ Le daemon user peut planter
Socket exposé Aucun par défaut $XDG_RUNTIME_DIR/docker.sock
Docker Swarm N/A ❌ Non supportĂ© en rootless
IntĂ©gration systemd ✅ Native (Quadlet) ⚠ Manuelle
SELinux ✅ Natif RHEL/Fedora ⚠ Non supportĂ© sur RHEL

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.

1.3 Les Pods Podman

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.

1.4 Volumes nommés vs bind mounts

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.

1.5 L'option --rm et la philosophie des conteneurs éphémÚres

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.


2. Prérequis et installation

2.1 Vérifier Podman

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

2.2 Vérifier la configuration rootless

# 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Ăźtre

2.3 Créer les volumes nommés

Pourquoi crĂ©er les volumes manuellement ? Podman crĂ©e automatiquement un volume nommĂ© au premier podman run qui le rĂ©fĂ©rence — podman volume create n'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/

3. Préparer les volumes persistants

3.1 Pourquoi deux volumes séparés ?

  • 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.

3.2 Vérifier l'accessibilité SELinux

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:s0

Avec les volumes nommĂ©s, aucune manipulation semanage manuelle n'est nĂ©cessaire — Podman s'en charge.


4. Créer et démarrer le Pod WordPress

Nous allons procéder en trois étapes :

  1. Créer le pod avec la configuration réseau
  2. Lancer MariaDB dans le pod
  3. Lancer WordPress dans le pod

4.1 Créer le pod

podman pod create \
  --name wp-blog \
  --publish 8088:80

Explication 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...     1

Le pod contient déjà 1 conteneur : le conteneur infra (pause).

4.2 Lancer le conteneur MariaDB

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

Explication 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 MySQL
  • docker.io/library/mariadb:11 : image officielle MariaDB, version fixĂ©e (Ă©vite les surprises en production)

⚠ SĂ©curitĂ© : Dans un contexte de production, utilisez podman secret pour 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 logs

4.3 Lancer 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

Point 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Ă©.


5. Vérification et accÚs à WordPress

5.1 Vérifier l'état du pod et des conteneurs

# 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-app

5.2 Accéder à WordPress

Ouvrir 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
  • Email

5.3 Tester la persistance des données

# 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Ă©s

L'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 --pod

AccĂ©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 --rm et les pods : --rm signifie 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.

5.4 Nettoyage manuel (si nécessaire)

# 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_data

6. Démarrage automatique avec systemd (Quadlet)

6.1 Pourquoi Quadlet plutĂŽt que podman generate systemd ?

podman 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-reload suffit
  • 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+

6.2 User Lingering — permettre le dĂ©marrage sans session

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=yes

Sans cette Ă©tape, vos conteneurs s'arrĂȘteraient Ă  la dĂ©connexion SSH.

6.3 Structure des fichiers Quadlet

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

6.4 Créer le répertoire Quadlet

mkdir -p ~/.config/containers/systemd

Pour aller plus loin : l'outil podlet gĂ©nĂšre automatiquement des fichiers Quadlet depuis une commande podman run ou un docker-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-reload

6.5 Fichier wp-blog.pod

Ce 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
EOF

Explication des sections :

  • [Unit] : section systemd standard (description, dĂ©pendances)
  • [Pod] : section spĂ©cifique Quadlet pour la configuration du pod
  • PublishPort : Ă©quivalent du --publish de podman pod create
  • [Install] avec WantedBy=default.target : active le service au boot. Pour les conteneurs rootless, on utilise default.target et non multi-user.target (qui ne fonctionne pas en mode user).

6.6 Fichier wp-mysql.container

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
EOF

Explication :

  • 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 service wp-blog-pod.service pour le pod.
  • Pod=wp-blog.pod : lie ce conteneur au pod dĂ©clarĂ© dans wp-blog.pod
  • TimeoutStartSec=120 : MariaDB peut mettre du temps Ă  s'initialiser (Ă©criture des fichiers DB au premier lancement), on lui donne 2 minutes

6.7 Fichier wp-app.container

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
EOF

Point 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).

6.8 Activer et démarrer les services

# 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 -f

6.9 Accéder à WordPress avec Quadlet

http://localhost:8088

Le service démarrera automatiquement à chaque boot de la machine, sans aucune connexion nécessaire (grùce au lingering).

6.10 Vérifier le démarrage automatique

# 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.service

7. Comparaison avec podman-compose

7.1 Qu'est-ce que podman-compose ?

podman-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-compose

7.2 Le fichier docker-compose.yml officiel de podman-compose

Le 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.yml

DiffĂ©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 utilisaient 127.0.0.1 car ils partagent le mĂȘme namespace rĂ©seau.

podman-compose et les pods : podman-compose crĂ©e automatiquement un pod en coulisse, nommĂ© d'aprĂšs le rĂ©pertoire de travail. Tu peux le constater avec podman pod ps aprĂšs un podman-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 par 127.0.0.1 comme 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.

7.3 Démarrer WordPress avec podman-compose

# 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 --volumes

7.4 Démarrer podman-compose au boot avec systemd

Voici 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.service

Explication des directives clés :

  • Type=oneshot : la commande se termine rapidement (lance les conteneurs en arriĂšre-plan avec -d), ce qui est normal
  • RemainAfterExit=yes : le service reste "actif" mĂȘme aprĂšs la fin de ExecStart, ce qui est correct pour un service de type oneshot
  • WorkingDirectory=%h/wordpress-compose : %h est le spĂ©cificateur systemd pour le rĂ©pertoire home de l'utilisateur

7.5 Tableau comparatif — Pod Podman vs podman-compose vs Quadlet

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+

7.6 Conclusion — Quand utiliser quoi ?

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

8. Aide-mémoire des commandes

Gestion du pod

# 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

Gestion des conteneurs

# 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

Gestion des volumes

# 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

Gestion systemd (Quadlet)

# 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

podman-compose

# 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-update

Podman rootless — Fix session systemd user (Fedora 43)

Prérequis

Vérifier les subuid/subgid :

grep <user> /etc/subuid /etc/subgid

Si absent :

sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 <user>

1. Activer le linger

sudo loginctl enable-linger <user>
ls /var/lib/systemd/linger/

2. Créer le répertoire runtime

sudo mkdir -p /run/user/<uid>
sudo chown <user>:<user> /run/user/<uid>
sudo chmod 700 /run/user/<uid>

3. Patcher le service systemd user

sudo systemctl edit user@<uid>.service
[Service]
Environment=XDG_RUNTIME_DIR=/run/user/<uid>

4. export also for shell

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 ~/.bashrc

5. Démarrer et activer

sudo systemctl daemon-reload
sudo systemctl start user@<uid>.service
sudo systemctl enable user@<uid>.service
sudo systemctl status user@<uid>.service

Références officielles

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment