Aller au contenu
Conteneurs & Orchestration medium

Pods Podman : regrouper des conteneurs comme une unité

24 min de lecture

logo podman

Un pod Podman regroupe plusieurs conteneurs qui partagent le même espace réseau. Ils communiquent via localhost, démarrent et s'arrêtent ensemble. C'est idéal pour les patterns sidecar (app + reverse proxy, app + agent de logs).

À la fin de ce guide, vous saurez :

  • Créer un pod avec plusieurs conteneurs
  • Comprendre ce qui est partagé et ce qui ne l'est pas
  • Exposer correctement les ports
  • Éviter les pièges classiques

Quatre entrées directes vers les questions les plus fréquentes sur les pods. Si vous découvrez la notion, suivez plutôt le guide dans l'ordre : chaque section s'appuie sur le vocabulaire posé par la précédente.

Le modèle « un conteneur, un processus » est une bonne règle, mais il laisse un cas non résolu : deux processus si étroitement liés qu'ils forment une seule application. Les faire cohabiter dans une même image casse l'isolation et complique les mises à jour ; les séparer sur deux réseaux distincts ajoute une latence et une configuration inutiles. Le pod est la réponse à cette situation intermédiaire.

Le problème : conteneurs qui doivent communiquer étroitement

Section intitulée « Le problème : conteneurs qui doivent communiquer étroitement »

Certains couples de conteneurs ne survivent pas l'un sans l'autre : un proxy qui n'a rien à servir, un agent de collecte de logs sans application à observer. Les traiter comme deux services indépendants oblige à gérer un réseau, une résolution de noms et un ordre de démarrage, pour un couplage qui est de toute façon total. Ces applications nécessitent plusieurs processus qui doivent :

  • Communiquer sur localhost (pas via le réseau)
  • Partager des volumes pour échanger des fichiers
  • Être démarrés/arrêtés ensemble

Exemples :

PatternConteneur principalSidecar
Reverse proxyApplicationnginx/envoy
LoggingApplicationFluentd/Vector
MonitoringApplicationPrometheus exporter
PHP-FPMnginxphp-fpm

Un pod est une unité de gestion qui enveloppe plusieurs conteneurs et met en commun une partie de leur isolation. Le terme vient de Kubernetes, dont Podman reprend le modèle pour permettre de tester localement les mêmes assemblages. Concrètement, un pod est un groupe de conteneurs qui partagent :

  • Le namespace réseau (même interface, même localhost)
  • Le namespace IPC (communication inter-processus)
  • Optionnellement des volumes

Structure d'un pod Podman : nginx + php-fpm + infra partageant localhost

La création d'un pod se fait en deux temps : d'abord le pod lui-même avec sa configuration réseau, ensuite les conteneurs qu'on y rattache avec --pod. Cet ordre n'est pas négociable, puisque le pod détient déjà l'espace réseau que les conteneurs vont rejoindre.

  1. Créer le pod

    Fenêtre de terminal
    podman pod create --name web-stack -p 8080:80

    Important : le port est exposé au niveau du pod, pas des conteneurs.

  2. Ajouter nginx

    Fenêtre de terminal
    podman run -d --pod web-stack --name nginx nginx:alpine
  3. Vérifier

    Fenêtre de terminal
    podman pod ps
    Résultat
    POD ID NAME STATUS CREATED INFRA ID # OF CONTAINERS
    abc123def456 web-stack Running 1 minute ago def456ghi789 2
  4. Tester

    Fenêtre de terminal
    curl http://localhost:8080

    Vous voyez la page d'accueil nginx.

  5. Voir les conteneurs du pod

    Fenêtre de terminal
    podman pod inspect web-stack --format '{{range .Containers}}{{.Name}} {{end}}'
    Résultat
    abc123def456-infra nginx

Notez qu'il y a 2 conteneurs : nginx + le conteneur "infra" (on y revient).

Un namespace est un mécanisme du noyau Linux qui donne à un processus sa propre vue d'une ressource : ses propres interfaces réseau, sa propre table de processus, son propre système de fichiers. L'isolation d'un conteneur repose entièrement sur ces cloisons. Un pod ne les supprime pas, il en met certaines en commun entre plusieurs conteneurs, et laisse les autres intactes.

Tous les conteneurs d'un pod partagent la même interface réseau. Concrètement :

  • Ils ont la même IP
  • Ils peuvent communiquer via localhost
  • Un conteneur peut accéder aux ports des autres sans configuration

Démonstration :

Fenêtre de terminal
# Créer un pod avec deux conteneurs
podman pod create --name demo-net
podman run -d --pod demo-net --name server \
python:3.12-alpine python -m http.server 9000
podman run -d --pod demo-net --name client \
alpine:3.21 sleep infinity
# Le client peut accéder au serveur via localhost
podman exec client wget -qO- http://localhost:9000
Résultat
<!DOCTYPE HTML>
<html>...

Sans pod, il faudrait un réseau dédié et utiliser le nom du conteneur.

Quand vous créez un pod, Podman démarre automatiquement un conteneur spécial appelé infra (ou "pause"). Ce conteneur ne fait rien d'utile en apparence, il dort. Mais c'est lui qui détient les namespaces réseau et IPC que tous les autres conteneurs du pod partagent.

Son rôle :

FonctionExplication
Maintenir les namespacesMême si tous les conteneurs s'arrêtent, le namespace réseau persiste
Ancrer l'IPL'IP du pod ne change pas quand un conteneur redémarre
LégerUtilise ~1 MB de mémoire
Fenêtre de terminal
# Voir le conteneur infra
podman ps --pod | grep infra
Résultat
d75b260a41e0 localhost/podman-pause:5.8.4-0 ... Up web-stack-infra web-stack

Le partage se limite au réseau et aux IPC. Chaque conteneur conserve son propre système de fichiers, sa propre vision des processus, et ses propres limites de ressources. C'est une différence importante avec les VM où tout est partagé par défaut.

RessourcePartagée ?Explication
RéseauMême interface, même localhost
IPCCommunication inter-processus
Volumes⚠️Seulement si montés explicitement
PID namespaceChaque conteneur a ses propres PIDs
FilesystemChaque conteneur a son propre rootfs
Mémoire/CPULimites par conteneur

Le système de fichiers, lui, n'est jamais mis en commun automatiquement. Pour échanger des fichiers, il faut monter explicitement le même volume dans chaque conteneur. Le suffixe :Z demande à Podman de réétiqueter le contenu pour SELinux, sans quoi l'accès est refusé sur Fedora, RHEL et Rocky Linux ; ro monte le volume en lecture seule côté consommateur.

Fenêtre de terminal
# Créer un pod avec volume partagé
podman pod create --name shared-data
# Conteneur qui écrit
podman run -d --pod shared-data \
-v data:/shared:Z \
--name writer \
alpine:3.21 sh -c 'while true; do date >> /shared/log.txt; sleep 5; done'
# Conteneur qui lit
podman run -d --pod shared-data \
-v data:/shared:Z,ro \
--name reader \
alpine:3.21 tail -f /shared/log.txt

Deux niveaux de gestion coexistent : les commandes podman classiques, qui visent un conteneur, et les commandes podman pod, qui visent le groupe entier. Savoir laquelle utiliser évite la confusion la plus fréquente, celle d'un pod affiché comme actif alors qu'aucun conteneur applicatif ne tourne.

Les commandes podman pod agissent sur tous les conteneurs du pod en une seule opération. C'est l'intérêt principal : gérer un groupe comme une unité.

CommandeEffet
podman pod start <pod>Démarre tous les conteneurs
podman pod stop <pod>Arrête tous les conteneurs
podman pod restart <pod>Redémarre tous les conteneurs
podman pod rm <pod>Supprime le pod ET tous ses conteneurs
podman pod pause <pod>Met en pause tous les conteneurs
podman pod unpause <pod>Reprend tous les conteneurs

La séquence ci-dessous crée deux conteneurs sans intérêt fonctionnel (sleep infinity les maintient simplement en vie) pour observer qu'un seul pod stop les arrête tous les deux :

Fenêtre de terminal
# Créer un pod avec 2 conteneurs
podman pod create --name lifecycle-demo
podman run -d --pod lifecycle-demo --name c1 alpine:3.21 sleep infinity
podman run -d --pod lifecycle-demo --name c2 alpine:3.21 sleep infinity
# Vérifier l'état
podman pod ps
podman ps --pod
# Arrêter le pod (arrête c1 et c2)
podman pod stop lifecycle-demo
# Vérifier
podman ps -a --pod | grep lifecycle
Résultat
abc123 alpine:3.21 Exited c1 lifecycle-demo
def456 alpine:3.21 Exited c2 lifecycle-demo

Le pod n'assure aucune supervision de ses membres : il fournit un espace réseau commun, pas une garantie de fonctionnement. Un conteneur qui s'arrête laisse donc le pod dans un état incohérent, actif en apparence mais amputé d'une de ses fonctions. Quand un conteneur dans un pod s'arrête inopinément :

SituationComportement
Un conteneur crashLes autres continuent
Tous les conteneurs crashentLe pod reste "Running" (infra actif)
Le conteneur infra crashLe pod est détruit

Important : contrairement à Kubernetes, Podman ne redémarre pas automatiquement les conteneurs crashés. Utilisez Quadlet ou systemd pour ça.

La publication de ports est le point sur lequel butent presque tous les débutants, parce qu'elle ne se comporte pas comme avec un conteneur isolé. La cause est directe : le conteneur infra détient le namespace réseau, donc c'est lui, et lui seul, qui peut publier un port vers l'hôte.

Les ports sont exposés au niveau du pod, pas des conteneurs individuels. La conséquence pratique est qu'il faut connaître les ports à publier au moment de créer le pod : les ajouter après coup impose de détruire le pod et de le recréer.

Fenêtre de terminal
# ✅ Correct : port sur le pod
podman pod create --name web -p 8080:80
podman run -d --pod web --name nginx nginx:alpine
# ❌ Incorrect : -p ignoré quand --pod est utilisé
podman run -d --pod web -p 9090:80 --name nginx nginx:alpine
# Warning: Le -p est ignoré !

L'option -p se répète autant de fois que nécessaire sur la commande de création, y compris pour des ports servis par des conteneurs différents du même pod :

Fenêtre de terminal
podman pod create --name multi-port \
-p 8080:80 \
-p 9000:9000 \
-p 3000:3000

Le modèle réseau d'un pod est volontairement simple : tout passe par localhost. C'est parfait pour des conteneurs couplés (nginx → php-fpm), mais insuffisant pour des architectures distribuées.

FonctionnalitéDisponible ?Alternative
localhost entre conteneurs-
Ports exposés à l'hôte-
Service discovery (DNS)Utiliser podman-compose ou des réseaux bridge
Load balancingTraefik, nginx reverse proxy

Le cas d'usage classique : nginx sert les fichiers statiques et fait proxy vers php-fpm pour le PHP. Les deux processus doivent voir les mêmes fichiers sources et dialoguer en FastCGI, un protocole binaire qui n'a aucun mécanisme de découverte de service. Un pod répond exactement à ces deux besoins avec un volume commun et une adresse fixe.

Le schéma montre le trajet d'une requête : elle entre par le port publié du pod, atteint nginx, qui bascule les fichiers .php vers php-fpm sur 127.0.0.1:9000. Les deux conteneurs montent le même répertoire de sources.

nginx + php-fpm dans un pod : proxy vers FastCGI avec volume partagé

Six étapes, à exécuter dans l'ordre : les fichiers de configuration doivent exister sur l'hôte avant d'être montés, et le pod avant les conteneurs.

  1. Créer le contenu PHP

    Fenêtre de terminal
    mkdir -p ~/lab-pod/html
    ~/lab-pod/html/index.php
    <?php
    echo "<h1>PHP dans un pod Podman</h1>";
    echo "<p>Hostname: " . gethostname() . "</p>";
    echo "<p>Date: " . date('Y-m-d H:i:s') . "</p>";
    phpinfo();
    ?>
  2. Configuration nginx

    ~/lab-pod/nginx.conf
    server {
    listen 80;
    server_name localhost;
    root /var/www/html;
    index index.php index.html;
    location ~ \.php$ {
    # php-fpm sur localhost:9000 (même pod)
    fastcgi_pass 127.0.0.1:9000;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    include fastcgi_params;
    }
    location / {
    try_files $uri $uri/ =404;
    }
    }
  3. Créer le pod avec le port

    Fenêtre de terminal
    podman pod create --name web-php -p 8080:80
  4. Lancer php-fpm

    Fenêtre de terminal
    podman run -d --pod web-php \
    --name php-fpm \
    -v ~/lab-pod/html:/var/www/html:Z \
    php:8.3-fpm-alpine
  5. Lancer nginx

    Fenêtre de terminal
    podman run -d --pod web-php \
    --name nginx \
    -v ~/lab-pod/html:/var/www/html:Z,ro \
    -v ~/lab-pod/nginx.conf:/etc/nginx/conf.d/default.conf:Z,ro \
    nginx:alpine
  6. Vérifier

    Fenêtre de terminal
    # État du pod
    podman pod ps
    # Conteneurs
    podman ps --pod
    # Tester
    curl http://localhost:8080/index.php

Trois contrôles suffisent à confirmer que l'assemblage est correct : l'appartenance des conteneurs au pod, l'ouverture du port FastCGI vue depuis nginx, et l'absence d'erreur au démarrage dans les journaux.

Fenêtre de terminal
# Vérifier que les conteneurs sont dans le même pod
podman pod inspect web-php --format '{{range .Containers}}{{.Name}} {{end}}'
# php-fpm nginx abc123-infra
# Vérifier que localhost:9000 fonctionne depuis nginx
podman exec nginx sh -c 'echo "test" | nc -z 127.0.0.1 9000 && echo "OK"'
# OK
# Logs
podman logs nginx
podman logs php-fpm

Podman supporte les deux : pods natifs et Compose. La question revient souvent : "Pourquoi utiliser un pod plutôt que Compose ?"

La réponse tient en une phrase : les pods sont pour les conteneurs qui doivent partager localhost, Compose pour les services qui communiquent via le réseau.

Lisez ce tableau en partant de votre contrainte la plus forte, pas en comptant les coches. Un seul critère bloquant, comme le besoin de résolution DNS entre services, suffit à trancher.

CritèrePodCompose
Conteneurs sur localhostIdéal⚠️ Possible mais pas natif
Services indépendantsIdéal
Stack multi-services (db + api + front)⚠️
Pattern sidecarIdéal⚠️
Compatibilité Kubernetes YAML
Service discovery (DNS)
Déjà un docker-compose.yml

Si vous ne deviez retenir qu'un critère, c'est le mode de communication attendu entre les composants.

  • Pod : conteneurs couplés qui doivent partager localhost
  • Compose : services découplés qui communiquent via le réseau

Cinq situations réelles et le choix qui s'impose, avec la raison qui le motive entre parenthèses.

SituationMeilleur choix
nginx + php-fpmPod (localhost:9000)
App + redis + postgresCompose (services séparés)
App + envoy sidecarPod (proxy localhost)
MicroservicesCompose (service discovery)
Test d'un YAML KubernetesPod (kube play)

Les pods Podman s'inspirent de Kubernetes, mais ne sont pas identiques. Podman est un outil local sans orchestrateur, les fonctionnalités qui dépendent d'un cluster (scheduling, service discovery, restart automatique) sont absentes.

Si vous connaissez Kubernetes, attention aux différences :

AspectPodman podsKubernetes pods
Restart automatique❌ Non✅ Oui (restartPolicy)
Service discovery DNS❌ Non✅ Oui
Load balancing❌ Non✅ Oui (Services)
Scheduling❌ Local uniquement✅ Multi-nodes
Health checks⚠️ Manuels✅ livenessProbe, readinessProbe
Resource limits⚠️ Par conteneur✅ Pod-level + container-level

Trois habitudes prises avec les pods Podman ne se transposent pas telles quelles, et trois réflexes acquis sur Kubernetes n'ont pas d'équivalent local.

  1. Pas de restart auto : en production, utilisez Quadlet/systemd
  2. Pas de DNS : utilisez localhost, pas des noms de service
  3. Ports au pod : pas de -p sur podman run --pod

Les symptômes se ressemblent tous : un service qui ne répond pas. La démarche consiste à remonter la chaîne dans l'ordre, du port publié par le pod jusqu'à l'interface d'écoute du processus dans le conteneur.

La plupart des problèmes avec les pods viennent de deux sources : les ports mal configurés, et les conteneurs qui ne sont pas dans le bon pod. Le tableau ci-dessous couvre 90% des cas.

ErreurCauseSolution
port already in usePort exposé sur un pod existantpodman pod rm <pod>
-p ignoré avec --podPorts = responsabilité du podCréer le pod avec -p
Conteneur ne voit pas l'autreMauvais port ou conteneur pas dans le podVérifier avec podman pod inspect
Connection refused localhostLe service n'écoute pas sur toutes les interfacesVérifier que le service écoute sur 0.0.0.0

Ces commandes se lancent dans l'ordre où elles sont listées : d'abord l'inventaire, puis l'inspection détaillée, enfin le test réseau depuis l'intérieur d'un conteneur. La dernière détruit le pod, ne l'exécutez qu'une fois le diagnostic terminé.

Fenêtre de terminal
# Lister les pods
podman pod ps
# Voir les conteneurs d'un pod
podman ps --pod --filter pod=<pod-name>
# Inspecter un pod
podman pod inspect <pod-name>
# Voir les ports d'un pod
podman pod inspect <pod-name> --format '{{.InfraConfig.PortBindings}}'
# Logs de tous les conteneurs du pod
podman pod logs <pod-name>
# Tester localhost depuis un conteneur
podman exec <container> wget -qO- http://localhost:<port>
# Nettoyer
podman pod rm -f <pod-name>

Un conteneur qui refuse de démarrer dans un pod échoue le plus souvent pour une raison qui n'a rien à voir avec le pod : image absente, commande erronée, volume introuvable. Les journaux du conteneur tranchent immédiatement entre les deux hypothèses.

Fenêtre de terminal
# Vérifier les logs
podman logs <container-name>
# Vérifier que le pod est Running
podman pod ps
# Recréer le pod
podman pod rm -f <pod-name>
podman pod create --name <pod-name> -p <ports>

Un pod créé à la main pour un essai est une chose ; un pod destiné à durer en est une autre. Trois points font la différence sur le long terme : des noms qu'on peut relire six mois plus tard, un découpage cohérent, et un mécanisme de démarrage qui ne dépend pas d'une session de terminal.

Podman attribue un nom aléatoire aux pods et conteneurs anonymes, ce qui rend toute commande ultérieure pénible. Nommez systématiquement, en préfixant les conteneurs par le nom du pod :

Fenêtre de terminal
# ✅ Noms explicites
podman pod create --name api-stack
podman run --pod api-stack --name api-server ...
podman run --pod api-stack --name api-cache ...
# ❌ Noms génériques
podman pod create --name pod1
podman run --pod pod1 --name container1 ...

Le périmètre d'un pod se décide sur un critère unique : les conteneurs partagent-ils vraiment un cycle de vie et une adresse ? Si la réponse est non pour l'un d'eux, il n'a rien à faire là.

  • Un pod = un groupe fonctionnel (app + sidecars)
  • Pas de pod monolithique avec 10 services indépendants
  • Volumes partagés seulement si nécessaire

Pour la production, ne lancez pas les pods manuellement. Utilisez :

  1. Pod = groupe de conteneurs partageant le réseau (même localhost)
  2. Conteneur infra : maintient les namespaces, ne pas supprimer
  3. Ports exposés au pod : -p sur podman pod create, pas sur run
  4. Pas de restart auto : utilisez Quadlet/systemd en production
  5. Pas de DNS : communication via localhost:<port>
  6. Pod vs Compose : pod pour couplage fort, Compose pour services séparés

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn