Ajout dynamique de mappages de ports à des conteneurs Docker en cours d’exécution

Ajout dynamique de mappages de ports à des conteneurs Docker en cours d’exécution

Réponse courte : vous ne pouvez pas. Docker ne propose aucun moyen pris en charge d’ajouter un port publié à un conteneur déjà en cours d’exécution. Les mappages de ports sont figés à la création du conteneur, et ni docker run ni docker container update ne peuvent les modifier par la suite. Le drapeau --publish-add n’existe que pour les services Swarm, pas pour les conteneurs autonomes.

Si vous ne pouvez pas redémarrer le conteneur, voici les options qui fonctionnent réellement :

Approche Ajoute un vrai mappage de port Docker ? Fonctionne sur Docker Desktop (macOS/Windows) ? Survit au redémarrage ?
Conteneur sidecar socat Non (transfert TCP) Oui Oui, si le sidecar redémarre
Proxy inverse (Nginx/Traefik/HAProxy) Non (proxy) Oui Oui
DNAT iptables hôte Non (règle NAT hôte) Non, Linux natif uniquement Non, sauf persistance
Recréer le conteneur avec -p Oui Oui Oui

La seule approche qui produit un véritable port publié géré par Docker — qui apparaît dans docker ps et docker port — est de recréer le conteneur. Tout le reste transfère le trafic à une couche supérieure ou inférieure à la comptabilité des ports de Docker. Choisissez en fonction de votre tolérance au redémarrage, et lisez les mises en garde ci-dessous avant d’exécuter quoi que ce soit en production.

Cet article explique chaque méthode, les commandes exactes et les points de rupture de chacune.

Contexte : comment fonctionne le mappage de ports Docker

Principes fondamentaux du mappage de ports de conteneurs

Dans Docker, la connexion entre le port interne d’un conteneur et le port de la machine hôte est facilitée par le mappage de ports. Habituellement, nous spécifions les mappages de ports à l’aide des paramètres -p ou --publish au démarrage d’un conteneur, comme illustré ci-dessous :

docker run -d -p 8080:80 nginx

La commande ci-dessus mappe le port 8080 de la machine hôte au port 80 à l’intérieur du conteneur. Ainsi, les utilisateurs externes peuvent accéder au service web s’exécutant dans le conteneur via le port 8080 de l’hôte.

Pourquoi Docker ne le permet pas

Une fois qu’un conteneur a démarré, Docker ne prend généralement pas en charge l’ajout dynamique de nouveaux mappages de ports. En d’autres termes, les mappages de ports initiaux restent fixes tout au long du cycle de vie du conteneur. Si vous devez ajouter d’autres mappages de ports, l’approche traditionnelle consiste à arrêter et redémarrer le conteneur, ce qui peut perturber les services et est inacceptable dans les environnements de production.

Les quatre solutions de contournement

Pour ajouter dynamiquement des mappages de ports à un conteneur en cours d’exécution, plusieurs méthodes peuvent être employées :

2.1 Conteneur sidecar qui transfère le port (recommandé)

Un conteneur distinct publie le nouveau port hôte et transfère le trafic vers le conteneur d’origine via un réseau Docker partagé. C’est l’option la plus sûre car le conteneur d’origine n’est jamais touché.

Une correction importante d’abord : vous ne pouvez pas combiner --network container:<name> avec -p. La documentation réseau de Docker indique que --publish, --publish-all et --expose ne sont pas pris en charge pour les conteneurs utilisant le mode réseau container:, car un tel conteneur n’a pas d’espace de noms réseau propre dans lequel mapper des ports. Tout guide qui vous dit d’exécuter docker run -p 8081:81 --net container:your-container ... est erroné, et Docker le rejettera.

Le modèle fonctionnel utilise un réseau défini par l’utilisateur afin que le sidecar puisse atteindre la cible par le nom du conteneur :

# 1. Créer un réseau et y attacher le conteneur en cours d’exécution (pas de redémarrage nécessaire)
docker network create app-net
docker network connect app-net your-container

# 2. Démarrer un sidecar socat qui publie le port 8081 et transfère vers le port 81 de la cible
docker run -d --name port-sidecar \
  --network app-net \
  --restart unless-stopped \
  -p 8081:81 \
  alpine/socat \
  TCP-LISTEN:81,fork,reuseaddr TCP:your-container:81

Notez que docker network connect fonctionne sur un conteneur en cours d’exécution, donc l’étape 1 n’entraîne aucun temps d’arrêt. Le sidecar écoute sur le port 81 dans son propre espace de noms, et -p 8081:81 publie cela sur l’hôte.

Mises en garde :

  • Il s’agit d’un transfert TCP, pas d’un mappage de port Docker. Il n’apparaîtra pas dans docker port your-container.
  • alpine/socat ne transfère que le TCP. Pour l’UDP, utilisez UDP-LISTEN/UDP, et pour HTTP avec routage basé sur l’hôte, préférez Nginx, Traefik, Caddy ou HAProxy.
  • Ajoutez --restart unless-stopped (comme ci-dessus) sinon le transfert disparaît au redémarrage.
  • Le saut supplémentaire coûte un peu de latence et ajoute un processus supplémentaire à surveiller.

2.2 Règle DNAT iptables hôte (Linux natif uniquement)

Sur un hôte Linux natif, vous pouvez ajouter une règle DNAT qui transfère un port hôte vers l’IP interne du conteneur :

# Obtenir l’IP du conteneur
CONTAINER_IP=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' your-container)

# Transférer le port hôte 8081 vers le port 81 du conteneur
sudo iptables -t nat -A DOCKER -p tcp --dport 8081 \
  -j DNAT --to-destination "${CONTAINER_IP}:81"

Cela vous donne un contrôle fin, mais comporte le plus de risques opérationnels de toutes les méthodes :

  • Docker Desktop sur macOS et Windows ne fonctionnera pas de cette façon. Les conteneurs s’exécutent dans une VM Linux, donc les règles iptables sur votre machine n’affectent pas le chemin réseau de Docker. Cette méthode est réservée au Linux natif.
  • Les règles ne persistent pas. Elles sont perdues au redémarrage, au rechargement du pare-feu ou lors d’une transition nftables/iptables. Utilisez le mécanisme de persistance du pare-feu de votre distribution si vous voulez qu’elles survivent.
  • Docker possède la chaîne DOCKER. Docker crée et gère ces règles à partir de la configuration des ports des conteneurs en cours d’exécution, et sa documentation indique que vous ne devez pas modifier les règles que Docker crée. Pour un filtrage personnalisé, Docker désigne DOCKER-USER comme emplacement réservé pour les règles définies par l’utilisateur, car les règles ajoutées à FORWARD sont traitées après celles de Docker.
  • Les IP des conteneurs ne sont pas stables. L’adresse change à chaque recréation du conteneur, laissant une règle obsolète qui ne redirige nulle part en silence.
  • Contourne la comptabilité de Docker. Le port n’apparaîtra pas dans docker ps ou docker port.

2.3 Exécution de socat directement sur l’hôte

Vous pouvez également exécuter socat comme un simple processus hôte plutôt que dans un conteneur :

socat TCP-LISTEN:8081,fork,reuseaddr TCP:<ip_du_conteneur>:81

Cela fonctionne sur Linux natif, où l’IP du conteneur est accessible depuis l’hôte. Sur Docker Desktop pour macOS et Windows, l’IP du conteneur n’est pas atteignable depuis votre machine, utilisez donc le sidecar de la section 2.1. Dans les deux cas, vous avez besoin d’un superviseur de processus (systemd, ou --restart sur le sidecar) pour survivre aux redémarrages, car un simple processus socat meurt avec son shell.

2.4 Recréer le service avec Docker Compose

C’est la seule méthode qui produit un véritable port publié géré par Docker. Ajoutez le mappage dans compose.yaml :

services:
  app:
    image: your-image:tag
    ports:
      - "8081:81"

Recréez ensuite ce service uniquement :

docker compose up -d app

Compose recrée le conteneur, il y a donc une brève interruption — ce n’est pas un changement à chaud. Notez que Docker moderne utilise docker compose (une sous-commande), pas l’ancien binaire autonome docker-compose. Conservez l’état dans des volumes nommés ou des montages de liaison pour qu’il survive à la recréation.

2.5 Modification des fichiers de configuration internes de Docker (non recommandé)

Vous trouverez des conseils pour éditer manuellement /var/lib/docker/containers/<id>/config.v2.json et hostconfig.json afin d’ajouter une entrée PortBindings, puis redémarrer le démon. Cela fonctionne parfois, mais traitez-le comme un dernier recours :

  • Ce sont des fichiers d’implémentation internes sans garantie de stabilité, pas une API prise en charge. Le format peut changer entre les versions de Docker.
  • Le démon maintient l’état du conteneur en mémoire. Modifier des fichiers sous un démon en cours d’exécution risque de voir vos modifications écrasées, et des modifications partielles peuvent laisser l’état réseau du conteneur incohérent avec ses métadonnées.
  • Vous devez arrêter le démon avant de modifier, ce qui affecte tous les conteneurs sur l’hôte.
  • Si live-restore est activé, les conteneurs continuent de fonctionner lors d’un redémarrage du démon — mais cela n’applique pas un mappage de port modifié. Live-restore ne change pas la règle selon laquelle un nouveau port publié nécessite une recréation du conteneur.

Si vous en êtes arrivé au point de modifier manuellement l’état du démon, recréer le conteneur avec le bon drapeau -p est plus rapide et plus sûr.

Conclusion

Docker ne prend pas en charge l’ajout d’un port publié à un conteneur en cours d’exécution, et aucune solution de contournement ne change cela. Ce que les méthodes ci-dessus vous offrent, c’est un moyen d’acheminer du nouveau trafic vers un conteneur que vous ne pouvez pas redémarrer.

Choisissez dans cet ordre :

  1. Pouvez-vous tolérer un bref redémarrage ? Recréez le conteneur avec le bon drapeau -p, ou ajoutez ports: dans compose.yaml et exécutez docker compose up -d. C’est la seule méthode qui produit un véritable mappage de port Docker.
  2. Impossible de redémarrer ? Utilisez le sidecar socat de la section 2.1, ou un proxy inverse si vous avez besoin de routage HTTP, TLS ou de vérifications de santé.
  3. Linux natif et besoin d’un transfert temporaire rapide ? Une règle DNAT iptables fonctionne, mais persistez-la délibérément et attendez-vous à ce que Docker interfère avec ses propres chaînes.
  4. Évitez de modifier manuellement les fichiers de configuration du démon.

Si les mappages de ports changent souvent, c’est généralement un signal de conception : placez un proxy inverse devant le service dès le départ, et laissez-le posséder les ports exposés à l’hôte afin que le cycle de vie des conteneurs et le routage restent indépendants.

Sources : Docker : Publication de ports, Docker : Modes de réseau des conteneurs, Docker : Filtrage de paquets et pare-feux, Docker et iptables, docker container port.

Vous pouvez visiter Novita AI pour des instances GPU et des API de modèles.