Aller au contenu
Conteneurs & Orchestration medium

Réseaux Podman : isoler et connecter vos conteneurs

16 min de lecture

logo podman

Ce guide vous apprend à isoler et connecter vos conteneurs en construisant une architecture 3 tiers complète. À la fin, vous saurez créer des réseaux privés, activer le DNS entre conteneurs, et exposer uniquement ce qui doit l’être.

Une mini-application 3 tiers avec isolation réseau :

ConteneurRéseau(x)Rôle
dbbackend (isolé)Base de données, invisible de l’extérieur
apibackend + frontendPont entre les deux mondes
webfrontendSeul conteneur exposé sur un port

Compétences acquises :

  • Créer un réseau isolé (--internal)
  • Activer le DNS pour résoudre les conteneurs par nom
  • Connecter un conteneur à plusieurs réseaux
  • Exposer un port sur localhost uniquement (sécurité)
  • Diagnostiquer quand ça ne marche pas

Avant de manipuler, comprenez ce qui se passe sous le capot.

Un réseau Podman = bridge + NAT + (optionnel) DNS

Section intitulée « Un réseau Podman = bridge + NAT + (optionnel) DNS »

Quand vous créez un réseau Podman, il crée :

  1. Un bridge : interface réseau virtuelle sur l’hôte
  2. Du NAT : les conteneurs accèdent à Internet via l’IP de l’hôte
  3. Un serveur DNS (optionnel) : résout les noms de conteneurs en IP

Podman crée automatiquement un réseau podman. Vérifions s’il a le DNS activé :

Fenêtre de terminal
podman network inspect --format '{{.DNSEnabled}}' podman
Résultat
false

Le DNS est désactivé par défaut sur le réseau podman. Les conteneurs ne peuvent pas se joindre par leur nom.

En mode rootless (sans sudo), Podman ne peut pas créer un vrai bridge. Il utilise :

  • pasta (défaut depuis Podman 5) : meilleure performance
  • slirp4netns (legacy) : plus lent mais compatible

Cela impacte les performances réseau et certaines fonctionnalités. Pour vérifier quel backend est utilisé :

Fenêtre de terminal
podman info --format '{{.Host.NetworkBackendInfo.Backend}}'

On commence par le plus restrictif : un réseau sans accès Internet pour la base de données.

L’option --internal crée un réseau isolé : les conteneurs ne peuvent pas atteindre Internet.

Fenêtre de terminal
podman network create --internal --subnet 172.30.0.0/24 backend

Vérifiez les propriétés :

Fenêtre de terminal
podman network inspect --format 'DNS: {{.DNSEnabled}}, Internal: {{.Internal}}' backend
Résultat
DNS: true, Internal: true

Le DNS est activé automatiquement sur les réseaux personnalisés. L’option --internal bloque l’accès extérieur.

Fenêtre de terminal
podman run -d --name db \
--network backend \
-e POSTGRES_PASSWORD=secret \
-e POSTGRES_DB=app \
docker.io/library/postgres:16-alpine

Vérifiez que le conteneur est bien sur le réseau backend :

Fenêtre de terminal
podman inspect db --format '{{range $k, $v := .NetworkSettings.Networks}}{{$k}}: {{$v.IPAddress}}{{"\n"}}{{end}}'
Résultat
backend: 172.30.0.2

La base de données ne doit pas pouvoir atteindre Internet :

Fenêtre de terminal
podman exec db ping -c 1 -W 2 8.8.8.8
Résultat
ping: sendto: Network is unreachable

Isolation confirmée. La DB est invisible depuis l’extérieur.

Maintenant, créons le réseau où sera exposé le frontend.

Ce réseau a accès à Internet (pas --internal) :

Fenêtre de terminal
podman network create --subnet 172.31.0.0/24 frontend

Vérifiez :

Fenêtre de terminal
podman network inspect --format 'DNS: {{.DNSEnabled}}, Internal: {{.Internal}}' frontend
Résultat
DNS: true, Internal: false

L’API doit communiquer avec la DB. On la place d’abord sur le réseau backend :

Fenêtre de terminal
podman run -d --name api \
--network backend \
docker.io/library/alpine:3.20 \
sleep 3600

Testons que l’API peut résoudre et atteindre la DB par son nom :

Fenêtre de terminal
podman exec api ping -c 2 db
Résultat
PING db (172.30.0.2): 56 data bytes
64 bytes from 172.30.0.2: seq=0 ttl=42 time=0.089 ms
64 bytes from 172.30.0.2: seq=1 ttl=42 time=0.112 ms

Le DNS fonctionne : db est résolu en 172.30.0.2.

Fenêtre de terminal
podman run -d --name web \
--network frontend \
docker.io/library/nginx:alpine

À ce stade, web ne peut pas joindre api ni db (ils sont sur des réseaux différents).

L’API doit être le seul pont entre frontend et backend. On la connecte aux deux réseaux.

Fenêtre de terminal
podman network connect frontend api

Vérifiez que l’API a maintenant deux interfaces :

Fenêtre de terminal
podman inspect api --format '{{range $k, $v := .NetworkSettings.Networks}}{{$k}}: {{$v.IPAddress}}{{"\n"}}{{end}}'
Résultat
backend: 172.30.0.3
frontend: 172.31.0.3

Depuis web vers api (doit fonctionner) :

Fenêtre de terminal
podman exec web ping -c 1 api
Résultat
PING api (172.31.0.3): 56 data bytes
64 bytes from 172.31.0.3: seq=0 ttl=42 time=0.098 ms

Depuis web vers db (doit échouer) :

Fenêtre de terminal
podman exec web ping -c 1 -W 2 db 2>&1 || echo "Isolation OK : web ne peut pas atteindre db"
Résultat
ping: bad address 'db'
Isolation OK : web ne peut pas atteindre db

Depuis api vers db (doit fonctionner) :

Fenêtre de terminal
podman exec api ping -c 1 db
Résultat
PING db (172.30.0.2): 56 data bytes
64 bytes from 172.30.0.2: seq=0 ttl=42 time=0.045 ms

Architecture multi-tier Podman avec isolation réseau

L’API est le seul conteneur capable de communiquer avec les deux mondes. La DB reste isolée.

Maintenant qu’on a une architecture isolée, exposons uniquement le frontend.

Les réseaux internes suffisent pour la communication entre conteneurs. L’exposition de ports n’est nécessaire que pour les services accessibles depuis l’hôte ou l’extérieur.

Règle 2 : en dev, exposez sur localhost uniquement

Section intitulée « Règle 2 : en dev, exposez sur localhost uniquement »
Fenêtre de terminal
# Arrêter et recréer web avec un port exposé
podman rm -f web
podman run -d --name web \
--network frontend \
-p 127.0.0.1:8080:80 \
docker.io/library/nginx:alpine

Le port 80 de nginx est uniquement accessible depuis localhost:8080 :

Fenêtre de terminal
curl -s http://127.0.0.1:8080 | head -3
Résultat
<!DOCTYPE html>
<html>
<head>

Depuis une autre machine du réseau, ce port n’est pas accessible.

Fenêtre de terminal
# Exposer sur une interface spécifique
podman run -d -p 192.168.1.100:8080:80 nginx:alpine
# Ou sur toutes les interfaces (à sécuriser par firewall)
podman run -d -p 8080:80 nginx:alpine

Après avoir démarré un conteneur avec -p, confirmez que le mapping est correct. Cette commande est essentielle pour le debug :

Fenêtre de terminal
podman port web
Résultat
80/tcp -> 127.0.0.1:8080

Le format indique : port interne → interface:port externe. Ici, le port 80 du conteneur est accessible uniquement sur 127.0.0.1:8080.

Troubleshooting : 6 réflexes quand ça ne marche pas

Section intitulée « Troubleshooting : 6 réflexes quand ça ne marche pas »

Quand un conteneur ne peut pas joindre un autre, procédez dans cet ordre pour isoler le problème.

Le conteneur est-il sur le bon réseau ? Cette commande liste tous les réseaux auxquels il est connecté :

Fenêtre de terminal
podman inspect <conteneur> --format '{{json .NetworkSettings.Networks}}' | jq

Le réseau a-t-il le DNS activé ? Est-il interne (sans Internet) ?

Fenêtre de terminal
podman network inspect <réseau> --format 'DNS: {{.DNSEnabled}}, Internal: {{.Internal}}, Subnet: {{range .Subnets}}{{.Subnet}}{{end}}'

Le conteneur sait-il où envoyer ses requêtes DNS ? Vérifiez que nameserver pointe vers une IP du réseau Podman :

Fenêtre de terminal
podman exec <conteneur> cat /etc/resolv.conf

Le nom est-il résolu en IP ? Si cette étape échoue, le problème est DNS (pas réseau) :

Fenêtre de terminal
podman exec <conteneur> nslookup <cible>
# ou
podman exec <conteneur> getent hosts <cible>

Si le DNS fonctionne mais la connexion échoue, testez la connectivité IP directe :

Fenêtre de terminal
# Ping (si ICMP disponible)
podman exec <conteneur> ping -c 1 <cible>
# Ou avec netcat (plus fiable pour tester un port spécifique)
podman exec <conteneur> nc -zv <cible> <port>

Si le conteneur écoute mais l’hôte n’y accède pas, vérifiez le binding :

Fenêtre de terminal
# Ports mappés par Podman
podman port <conteneur>
# Ports en écoute sur l'hôte
ss -tlnp | grep <port>
SymptômeCause probableSolution
bad address 'nom'DNS désactivé ou réseaux différentsVérifier DNSEnabled, utiliser même réseau
Connection refusedService non démarré ou mauvais portVérifier podman logs, tester port
Network unreachableRéseau --internal sans routeNormal si isolation voulue
Port non accessibleBinding sur 127.0.0.1Vérifier podman port, ajuster binding

Après avoir validé l’architecture, supprimez les ressources créées pour libérer les ports et les adresses IP :

Fenêtre de terminal
podman rm -f db api web
podman network rm backend frontend

Les volumes anonymes créés par PostgreSQL sont automatiquement supprimés avec -f. Pour un nettoyage complet, utilisez podman system prune.

Les alias DNS permettent à un conteneur de répondre à plusieurs noms. C’est utile pour :

  • Compatibilité : votre code attend redis mais le conteneur s’appelle redis-cache
  • Migration : pointer temporairement deux noms vers le même service
  • Généricité : utiliser cache au lieu d’un nom spécifique

Ajoutez des noms DNS alternatifs avec --network-alias :

Fenêtre de terminal
podman run -d --name redis-cache \
--network frontend \
--network-alias cache \
--network-alias redis \
docker.io/library/redis:alpine

Le conteneur répond maintenant à trois noms : redis-cache (nom du conteneur), cache et redis (alias). Tous résolvent vers la même IP.

  1. Créez toujours un réseau dédié : le réseau podman n’a pas de DNS activé
  2. --internal pour isoler : la DB n’a pas besoin d’Internet
  3. Multi-network pour les ponts : l’API connecte frontend et backend
  4. Exposez sur localhost en dev : -p 127.0.0.1:port:port
  5. DNS explicite : utilisez --dns-enabled en prod pour éviter les surprises
  6. 6 commandes de debug : inspect conteneur, inspect réseau, resolv.conf, ping/nc, podman port, ss

Ces modes sont rarement nécessaires. Utilisez-les uniquement si vous avez un besoin spécifique.

Le conteneur partage directement le réseau de l’hôte :

Fenêtre de terminal
podman run --rm --network host alpine ip addr show

Quand l’utiliser : performances réseau critiques (serveur DNS, proxy haute charge).

Risque : aucune isolation réseau, le conteneur peut écouter sur n’importe quel port.

Aucun réseau, uniquement loopback :

Fenêtre de terminal
podman run --rm --network none alpine ip addr show

Quand l’utiliser : jobs de calcul sans besoin réseau, sécurité maximale.

Assignez une IP fixe dans un réseau avec subnet :

Fenêtre de terminal
podman run -d --name db-fixed \
--network backend \
--ip 172.30.0.100 \
alpine sleep 300

Quand l’utiliser : intégration avec des outils externes qui attendent une IP fixe.

Préférez le DNS : plus flexible et maintenable.

FormatDescription
-p 8080:8080 → 8080 sur toutes les interfaces
-p 127.0.0.1:8080:8080 → 8080 sur localhost uniquement
-p 8080 → port aléatoire
-p 8080-8090:80-90Plage de ports
CommandeDescription
podman network create <nom>Créer un réseau
podman network create --internal <nom>Créer un réseau isolé
podman network lsLister les réseaux
podman network inspect <nom>Détails d’un réseau
podman network rm <nom>Supprimer un réseau
podman network pruneSupprimer les inutilisés
podman network connect <net> <ctr>Connecter un conteneur
podman network disconnect <net> <ctr>Déconnecter
podman port <conteneur>Voir les ports mappés

Ce site vous est utile ?

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

Je maintiens +700 guides gratuits, sans pub ni tracing. Aujourd'hui, ce site ne couvre même pas mes frais d'hébergement, d'électricité, de matériel, de logiciels, mais surtout de cafés.

Un soutien régulier, même symbolique, m'aide à garder ces ressources gratuites et à continuer de produire des guides de qualité. Merci pour votre appui.