Accueil/Réseaux, Systèmes & Cybersécurité /Chapitre 18 /Exercices corrigés

Docker — les deux TP

Deux sujets corrigés question par question : l'initiation (parties 1 à 6) et le réseau (parties 7 à 13). Les commandes sont à taper, pas à lire : un TP ne se révise pas, il se refait. Le cours · La fiche

1Introduction — le moteur répond-il ?

1Vérifier que Docker fonctionne Facile

1.1 Lancer docker --version puis docker info. Relever le nombre de conteneurs et d'images présents.

✅ Correction
bash
docker --version
docker info

docker --version ne dit qu'une chose : la commande existe. C'est docker info qui interroge le moteur — s'il répond, tout le reste du TP est possible.

Les deux lignes à relever sont en haut de sa sortie :

sortie
Client:
 Version:    27.3.1
...
Server:
 Containers: 0
  Running: 0
  Paused: 0
  Stopped: 0
 Images: 0

Sur une installation neuve, les deux valent 0. Les relever maintenant a un intérêt : à la fin du TP, on comparera, et on verra ce que l'on a oublié de nettoyer.

⚠️ Cannot connect to the Docker daemon

Docker Desktop n'est pas lancé (macOS, Windows), ou le service est arrêté (sudo systemctl start docker, Linux), ou l'utilisateur n'est pas dans le groupe docker et la session n'a pas été rouverte. La section 2 du cours détaille les trois cas.

2Lancer deux fois hello-world Facile

1.2 Exécuter docker run hello-world, puis relancer la même commande. Qu'est-ce qui change dans le temps d'exécution et dans les messages ? Expliquer pourquoi.

✅ Correction
bash
docker run hello-world

La première fois, Docker ne trouve pas l'image localement, va la chercher sur le registre public, et le dit :

sortie
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
c1ec31eb5944: Pull complete
Digest: sha256:...
Status: Downloaded newer image for hello-world:latest

Hello from Docker!

La seconde fois, ces cinq premières lignes disparaissent et l'exécution est quasi instantanée.

sortie
Hello from Docker!
🧭 L'explication attendue

Une image ne se télécharge qu'une fois : elle est ensuite dans le cache local de la machine, et docker run la réutilise. Le temps de la première exécution est presque entièrement du téléchargement, pas du démarrage — un conteneur, lui, démarre en quelques dizaines de millisecondes.

Conséquence pratique sur un serveur : on pull l'image avant la fenêtre de déploiement, pour que la bascule soit immédiate.

💡 Deux conteneurs, pas un

Vous venez de créer deux conteneurs, tous deux terminés. docker ps -a les montre à l'état Exited (0) : un conteneur qui a fini son travail ne disparaît pas tout seul.

2Manipuler des images

3Télécharger, lister, supprimer Facile

2.1 à 2.3 et 2.5 Télécharger alpine, nginx et httpd sans les exécuter. Lister les images locales en relevant pour chacune son nom, son tag, son identifiant et sa taille. Supprimer httpd.

✅ Correction
bash
docker pull alpine
docker pull nginx
docker pull httpd

docker images

docker rmi httpd
sortie
REPOSITORY    TAG       IMAGE ID       CREATED        SIZE
nginx         latest    3b25b682ea82   2 weeks ago    192MB
httpd         latest    f32c0b1d2e4a   3 weeks ago    148MB
alpine        latest    91ef0af61f39   5 weeks ago    7.8MB

Les identifiants et les tailles exactes dépendent du jour où vous téléchargez : ce sont des images qui évoluent. L'ordre de grandeur, lui, est stable — et c'est lui qui compte : Alpine est vingt-cinq fois plus petite que nginx.

💡 pull ne lance rien

C'est tout l'intérêt de la question : séparer avoir l'image et exécuter un conteneur. Après ces trois commandes, docker ps -a est toujours vide.

4Le tag, ou pourquoi il y a deux lignes « nginx » Moyen

2.4 Télécharger nginx:1.21, relancer docker images. Combien de lignes pour nginx ? Qu'est-ce que cela apprend sur la notion de tag ?

✅ Correction
bash
docker pull nginx:1.21
docker images
sortie
REPOSITORY    TAG       IMAGE ID       CREATED        SIZE
nginx         latest    3b25b682ea82   2 weeks ago    192MB
nginx         1.21      ea335eea17ab   3 years ago    141MB
alpine        latest    91ef0af61f39   5 weeks ago    7.8MB

Deux lignes, avec deux IMAGE ID différents.

🧭 L'explication attendue

Le tag fait partie de l'identité de l'image. Un dépôt (nginx) contient plusieurs versions, chacune désignée par un tag. nginx tout court n'est pas « le dépôt » : c'est un raccourci pour nginx:latest.

Noter aussi la colonne CREATED : « 3 years ago » pour la 1.21. C'est la date de fabrication de l'image, pas celle du téléchargement.

⚠️ latest ne veut pas dire « la plus récente »

C'est le tag employé par défaut, rien de plus. En production on épingle toujours une version explicite : un déploiement qui change tout seul parce qu'un éditeur a republié latest est un incident, pas une mise à jour.

⚠️ Supprimer une version précise

docker rmi nginx ne supprime que nginx:latest. Pour l'autre : docker rmi nginx:1.21. Le tag se précise aussi à la suppression.

3Manipuler des conteneurs

5Entrer dans une Alpine, et comprendre ps -a Facile

3.1 et 3.2 Lancer un conteneur interactif Alpine avec un terminal. À l'intérieur : ls, cat /etc/os-release, puis exit. Relancer docker ps puis docker ps -a : le conteneur apparaît-il dans les deux ? Justifier.

✅ Correction
bash
docker run -it alpine sh
sortie
/ # ls
bin  dev  etc  home  lib  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
/ # cat /etc/os-release
NAME="Alpine Linux"
ID=alpine
VERSION_ID=3.20.3
PRETTY_NAME="Alpine Linux v3.20"
/ # exit
bash
docker ps      # vide
docker ps -a   # le conteneur alpine, a l'etat Exited
🧭 La justification attendue

Il n'apparaît que dans docker ps -a. docker ps ne liste que les conteneurs en cours d'exécution ; celui-ci est arrêté.

Et il est arrêté parce qu'un conteneur vit tant que son processus principal vit. Ici, ce processus était le sh qu'on a quitté par exit : en le quittant, on a arrêté le conteneur.

Le conteneur n'a pas disparu pour autant. Il garde sa couche d'écriture et occupe de la place, jusqu'à un docker rm.

💡 -i et -t séparément

-i garde l'entrée standard ouverte, -t alloue un pseudo-terminal. Sans -t on n'a pas d'invite ; sans -i on ne peut rien taper. Les deux vont presque toujours ensemble, d'où -it.

6Un nginx en arrière-plan, et son statut sans docker ps Moyen

3.3 et 3.4 Lancer un nginx détaché, nommé mon-serveur. Puis, sans utiliser docker ps, retrouver son statut à l'aide d'une seule commande, et expliquer la démarche.

✅ Correction
bash
docker run -d --name mon-serveur nginx

La commande rend immédiatement la main et affiche l'identifiant complet du conteneur. Pour le statut, sans docker ps :

bash
docker inspect -f '{{.State.Status}}' mon-serveur
sortie
running
🧭 La démarche

docker inspect rend toute la fiche technique du conteneur en JSON — configuration, réseau, montages, état. L'option -f applique un gabarit Go qui n'en extrait qu'un champ : .State.Status.

Sans l'option, on voit l'objet entier et on peut chercher la bonne clé :

bash
docker inspect mon-serveur | grep -i status

L'intérêt de la question n'est pas la syntaxe du gabarit, c'est de découvrir qu'inspect est la source et que ps n'en est qu'un affichage condensé.

7Journaux, shell dans le conteneur, et le fichier qui survit Moyen

3.5 à 3.7 Afficher les journaux de mon-serveur. Ouvrir un terminal dans le conteneur déjà lancé, aller dans /usr/share/nginx/html et modifier index.html. Quitter, arrêter puis redémarrer le conteneur. Le fichier modifié a-t-il survécu ? Formuler une hypothèse.

✅ Correction
bash
docker logs mon-serveur

docker exec -it mon-serveur bash
bash
# a l'interieur du conteneur
cd /usr/share/nginx/html
echo "Bonjour NetSolutions" > index.html
cat index.html
exit
bash
docker stop mon-serveur
docker start mon-serveur
docker exec mon-serveur cat /usr/share/nginx/html/index.html
sortie
Bonjour NetSolutions
🧭 L'hypothèse attendue

Oui, le fichier a survécu. stop arrête le processus principal ; il ne détruit pas le conteneur. La couche d'écriture du conteneur — celle qui enregistre tout ce qui a été modifié par rapport à l'image — est toujours là, et start la retrouve.

Ce serait l'inverse après un docker rm : cette couche serait effacée, et un nouveau conteneur repartirait de la page d'accueil nginx d'origine. C'est précisément ce que démontre la partie 5.

⚠️ exec, pas run

docker run -it nginx bash créerait un deuxième conteneur, vierge, dans lequel le serveur ne tourne même pas. On y modifierait un fichier sans aucun effet sur mon-serveur. C'est l'erreur la plus coûteuse du TP, parce qu'elle ne produit aucun message.

8Supprimer, et ce que rm refuse Facile

3.8 Supprimer le conteneur. Que se passe-t-il si on essaie de supprimer un conteneur encore en cours d'exécution ? Quelle option force la suppression ?

✅ Correction
bash
docker stop mon-serveur
docker rm mon-serveur

Sur un conteneur qui tourne encore :

sortie
Error response from daemon: cannot remove container "mon-serveur":
container is running: stop the container before removing or force remove

L'option qui force est -f :

bash
docker rm -f mon-serveur     # arrete puis supprime, d'un coup
💡 Le refus est une protection, pas une contrariété

Supprimer un conteneur actif, c'est couper un service sans prévenir. Docker demande donc un geste explicite. -f envoie un SIGKILL immédiat, sans les dix secondes de grâce de stop : une base de données peut en garder une transaction incomplète.

4Exposer un service sur un port

9Publier 8080, puis 8081, puis échouer sur 8080 Moyen

4.1 à 4.4 Lancer nginx en publiant son port 80 sur le 8080 de la machine, puis vérifier dans un navigateur. Lancer un second nginx sur 8081 : les deux conteneurs peuvent-ils utiliser le port 80 en même temps, et pourquoi n'y a-t-il pas de conflit ? Enfin, tenter un troisième sur 8080 déjà pris : expliquer le message.

✅ Correction
bash
docker run -d --name web  -p 8080:80 nginx
curl http://localhost:8080

docker run -d --name web2 -p 8081:80 nginx
curl http://localhost:8081
sortie
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
🧭 Pourquoi aucun conflit sur le port 80

Chaque conteneur possède sa propre pile réseau : son port 80 n'est pas le port 80 de la machine. Les deux peuvent donc écouter sur 80 chez eux. Ce qui doit être unique, c'est le port de l'hôte — d'où 8080 et 8081.

C'est la même mécanique que le PAT : une redirection, avec une correspondance entre un port public et un service interne.

Le troisième :

bash
docker run -d -p 8080:80 --name web3 nginx
sortie
docker: Error response from daemon: driver failed programming external
connectivity on endpoint web3: Bind for 0.0.0.0:8080 failed: port is already allocated.
🧭 Lire le message

Bind for 0.0.0.0:8080 failed — Docker a demandé au système de réserver le port 8080 sur toutes les interfaces de l'hôte, et le système a refusé : web l'occupe déjà. Côté conteneur, rien n'empêchait web3 d'écouter sur 80.

Le conteneur est tout de même créé, à l'état Created : il apparaît dans docker ps -a et il faudra le supprimer.

⚠️ La page ne s'affiche pas ?

Trois causes, dans l'ordre : on a inversé -p 80:8080 · le conteneur s'est arrêté (docker ps -a le dira) · le TP est fait sur une machine distante ou en ligne, et localhost y désigne cette machine-là, pas la vôtre.

5Persister des données avec un volume

10Un volume qui survit à la destruction du conteneur Difficile

5.1 à 5.4 Créer un volume nommé donnees-web, lancer un nginx sur le port 8082 en l'associant au dossier servi, modifier index.html, puis supprimer complètement le conteneur. Relancer un conteneur de nom différent avec le même volume : la modification est-elle là ? Qu'est-ce que cela démontre ?

✅ Correction
bash
docker volume create donnees-web

docker run -d --name web-persistant -p 8082:80 \
           -v donnees-web:/usr/share/nginx/html nginx

docker exec web-persistant sh -c 'echo "Bonjour NetSolutions" > /usr/share/nginx/html/index.html'
curl http://localhost:8082
sortie
Bonjour NetSolutions
bash
docker stop web-persistant
docker rm   web-persistant          # le conteneur n'existe PLUS

docker run -d --name web-persistant-v2 -p 8082:80 \
           -v donnees-web:/usr/share/nginx/html nginx
curl http://localhost:8082
sortie
Bonjour NetSolutions
🧭 Ce que cela démontre

Un volume ne dépend d'aucun conteneur. Il est créé séparément, il est monté par un conteneur le temps que celui-ci vit, et il lui survit. Plusieurs conteneurs peuvent monter le même, successivement ou en même temps.

La relation est donc l'inverse de ce qu'on croit spontanément : ce n'est pas le conteneur qui possède ses données, c'est le volume qui prête les siennes à un conteneur.

C'est la réponse à la question 3.7 vue de l'autre côté : sans volume, la modification mourait avec le conteneur ; avec un volume, elle lui survit.

⚠️ Le volume masque le contenu de l'image

Au tout premier montage, le volume étant vide, Docker y recopie la page d'accueil nginx d'origine. Ensuite, le volume n'est plus vide et il remplace entièrement le dossier de l'image. Si un jour une mise à jour d'image n'apparaît pas, c'est souvent un volume qui la cache.

bash
docker volume ls
docker stop web-persistant-v2 && docker rm web-persistant-v2
docker volume rm donnees-web

L'ordre de la question 5.5 n'est pas négociable : docker volume rm refuse tant qu'un conteneur, même arrêté, l'utilise.

6Nettoyage, et les trois mots

11Tout nettoyer proprement Moyen

6.1 et 6.2 Lister tous les conteneurs encore présents, puis proposer et exécuter les commandes qui nettoient tout : arrêt et suppression des conteneurs restants, suppression des images inutiles.

✅ Correction
bash
docker ps -a

Deux manières, et la seconde est la bonne :

bash
# a la main, avec les identifiants
docker stop $(docker ps -q)       # arreter tout ce qui tourne
docker rm   $(docker ps -aq)      # supprimer tous les conteneurs
docker rmi  $(docker images -q)   # supprimer toutes les images
bash
# avec les commandes de menage de Docker
docker container prune -f
docker image prune -a -f
docker volume prune -f
docker system df                  # pour verifier ce qu'il reste
🧭 Pourquoi prune plutôt que la substitution

$(docker ps -q) échoue quand la liste est vide : la commande reçoit zéro argument et répond requires at least 1 argument. Les prune gèrent ce cas, demandent confirmation, et disent combien d'octets ont été récupérés.

-q ne rend que les identifiants, un par ligne : sans lui, on passerait aussi les en-têtes de colonnes à la commande suivante.

⚠️ docker system prune -a --volumes

Cette variante emporte aussi les volumes inutilisés — c'est-à-dire les données du projet sur lequel vous ne travaillez pas aujourd'hui. Sur votre machine de TP, aucun risque ; prenez tout de même l'habitude de lancer docker system df avant.

12Image, conteneur, volume — et ce qui persiste Moyen

6.3 En quelques lignes, distinguer clairement les trois notions manipulées, en précisant pour chacune si elle est persistante par défaut.

✅ Correction
NotionDéfinitionPersistant par défaut ?
Image Un modèle figé, en lecture seule, qui contient l'application et tout ce dont elle a besoin. On ne l'exécute pas : on en dérive des conteneurs. Oui — elle reste dans le cache local jusqu'à un docker rmi.
Conteneur Une exécution d'une image, avec sa propre couche d'écriture et sa propre pile réseau. C'est l'objet vivant et jetable. Non — sa couche d'écriture survit à un stop/start, mais disparaît au docker rm.
Volume Une zone de stockage gérée par Docker, extérieure aux conteneurs, qu'un conteneur monte à un emplacement donné. Oui — il survit à la suppression des conteneurs qui l'utilisent, et ne part qu'avec un docker volume rm.
🧭 La phrase qui résume les trois

« L'image est le modèle, le conteneur est l'exécution, le volume est la mémoire. Seuls le modèle et la mémoire survivent ; l'exécution est faite pour être jetée. »

C'est aussi ce qui fait la force du procédé : un conteneur qu'on peut détruire sans rien perdre est un conteneur qu'on peut remplacer à chaque mise à jour, sans sauvegarde et sans migration.

7Découvrir les réseaux de Docker

Second TP, autre sujet : comment les conteneurs se parlent. Les parties 7 à 13 reprennent le TP « Le réseau avec Docker », partie par partie. Toutes les sorties qui suivent ont été obtenues sur une machine, le 15 octobre 2026 — elles ne sont pas recopiées d'une documentation.

13Lister et inspecter les réseaux Facile

1.1 et 1.2 Lister les réseaux présents, en relevant le nom et le pilote de chacun. Inspecter le réseau bridge : relever son sous-réseau et sa passerelle. La liste Containers est-elle vide ?

✅ Correction
bash
docker network ls
docker network inspect bridge
sortie
NETWORK ID     NAME      DRIVER    SCOPE
6bca5b86a5e3   bridge    bridge    local
a647ec3084f5   host      host      local
b002c30b9b60   none      null      local

Sur une installation neuve, inspect donne un sous-réseau 172.17.0.0/16, une passerelle 172.17.0.1, et une liste Containers vide — aucun conteneur ne tourne encore. Pour n'en lire que l'essentiel :

bash
docker network inspect bridge --format '{{json .IPAM.Config}}'
sortie
[{"Subnet":"172.17.0.0/16","Gateway":"172.17.0.1"}]
Question 1.1 — les trois réseaux livrés avec Docker
Réseau / piloteRôle
bridge / bridgeLe réseau par défaut. Chaque conteneur y reçoit une adresse privée et sort vers l'extérieur par traduction d'adresse. C'est celui qu'on obtient sans rien demander.
host / hostPas d'isolation réseau : le conteneur partage directement la pile réseau de la machine. Pas d'adresse à lui, pas de publication de port à faire — mais pas de cloisonnement non plus.
none / nullAucune interface, sauf lo. Pour un conteneur qui ne doit parler à personne : un calcul isolé, l'analyse d'un fichier douteux.

Le pilote bridge apparaît deux fois dans ce TP : une fois comme nom du réseau par défaut, une fois comme pilote des réseaux qu'on crée soi-même. Ce sont deux choses différentes, et la partie 9 montre à quel point elles ne se comportent pas pareil.

8Le réseau bridge par défaut, et ses deux défauts

14Deux conteneurs, par adresse puis par nom Facile

2.1 à 2.5 Lancer deux conteneurs Alpine sans préciser de réseau, relever leurs adresses, puis tester la communication par adresse et par nom. Observer /etc/resolv.conf.

✅ Correction
bash
docker run -dit --name c1 alpine
docker run -dit --name c2 alpine
docker exec c1 ip addr show eth0
sortie
11: eth0@if17: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 65535 qdisc noqueue state UP
    link/ether 82:a5:60:83:81:7f brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever

c1 reçoit 172.17.0.2, c2 172.17.0.3 : le réseau bridge distribue dans l'ordre d'arrivée, à partir de l'adresse qui suit la passerelle. Par adresse, la communication passe :

bash
docker exec c1 ping -c 2 172.17.0.3
sortie
PING 172.17.0.3 (172.17.0.3): 56 data bytes
64 bytes from 172.17.0.3: seq=0 ttl=64 time=0.110 ms
64 bytes from 172.17.0.3: seq=1 ttl=64 time=0.199 ms

--- 172.17.0.3 ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss

Par nom, elle ne passe pas :

bash
docker exec c1 ping -c 2 c2
sortie
ping: bad address 'c2'

La raison est dans /etc/resolv.conf : le conteneur n'a aucun résolveur Docker, il pointe sur celui de la machine hôte.

sortie
# Generated by Docker Engine.
nameserver 192.168.65.7

# Based on host file: '/etc/resolv.conf' (legacy)
💡 L'adresse du résolveur dépend de l'installation

Ici 192.168.65.7 : c'est le résolveur de la machine virtuelle de Docker Desktop. Sur un Linux natif ce serait l'adresse du résolveur du poste. Ce qui compte n'est pas le chiffre, c'est ce qu'il n'est pas : 127.0.0.11, le résolveur interne de Docker — et la partie 9 montre où celui-là apparaît.

15Les adresses ne tiennent pas Moyen

2.6 Arrêter les deux conteneurs, les redémarrer dans l'ordre inverse, et relever de nouveau leurs adresses.

✅ Correction
bash
docker stop c1 c2
docker start c2
docker start c1
docker exec c1 ip -4 -o addr show eth0
docker exec c2 ip -4 -o addr show eth0
sortie
11: eth0    inet 172.17.0.3/16 ...   (c1)
11: eth0    inet 172.17.0.2/16 ...   (c2)

Les deux adresses ont été échangées. c1 avait 172.17.0.2, il a maintenant 172.17.0.3 : l'adresse ne suit pas le conteneur, elle suit l'ordre de démarrage.

⚠️ Question 2.2 — pourquoi c'est grave

Une application configurée avec BDD_HOST=172.17.0.2 fonctionne jusqu'au premier redémarrage de la machine. Ce jour-là, l'adresse désigne un autre conteneur — ou personne. Une panne qui n'arrive qu'après un reboot est la plus coûteuse à diagnostiquer, parce que rien n'a changé dans le code.

C'est tout l'enjeu des deux questions : sur le réseau par défaut, les noms ne résolvent pas et les adresses bougent. Il ne reste donc aucun moyen fiable de désigner un conteneur. La partie 9 règle les deux d'un coup.

9Un réseau personnalisé : les noms se mettent à marcher

16Créer son réseau et y parler par nom Facile

3.1 à 3.6 Créer un réseau avec son propre sous-réseau et sa passerelle, y lancer deux conteneurs, relever leurs adresses, tester la communication par nom et identifier le serveur DNS qui répond.

✅ Correction
bash
docker rm -f c1 c2
docker network create --subnet 192.168.50.0/24 --gateway 192.168.50.254 reseau-lan
docker run -dit --name c1 --network reseau-lan alpine
docker run -dit --name c2 --network reseau-lan alpine
docker exec c1 ip -4 -o addr show eth0
sortie
11: eth0    inet 192.168.50.1/24 ...   (c1)
11: eth0    inet 192.168.50.2/24 ...   (c2)
⚠️ Le sujet annonce .2 et .3 — la machine donne .1 et .2

Ce n'est pas une erreur du sujet, c'est une conséquence de la passerelle. Sur le réseau bridge, la passerelle occupe 172.17.0.1, donc la distribution commence à .2. Ici la passerelle a été placée en 192.168.50.254 : .1 est libre, et c'est elle qui est attribuée la première.

La première adresse distribuée dépend d'où est la passerelle. Un schéma qui annonce des adresses se vérifie toujours avec ip addr — c'est la machine qui a le dernier mot.

Par nom, cette fois, ça passe :

bash
docker exec c1 ping -c 2 c2
docker exec c1 nslookup c2
sortie
PING c2 (192.168.50.2): 56 data bytes
64 bytes from 192.168.50.2: seq=0 ttl=64 time=0.097 ms
2 packets transmitted, 2 packets received, 0% packet loss

Server:    127.0.0.11
Address:   127.0.0.11:53
Name:  c2
Address: 192.168.50.2

Question 3.1 — la différence majeure : sur un réseau personnalisé, Docker active son résolveur interne. Chaque conteneur y est enregistré sous son nom, et /etc/resolv.conf ne pointe plus sur l'hôte mais sur 127.0.0.11.

sortie
nameserver 127.0.0.11
options ndots:0

# Based on host file: '/etc/resolv.conf' (internal resolver)
# ExtServers: [host(192.168.65.7)]

Question 3.2 — qui répond : 127.0.0.11 est une adresse de bouclage du conteneur, pas un serveur du réseau. Docker y place un petit résolveur qui connaît les noms des conteneurs du réseau ; tout ce qu'il ignore, il le renvoie au résolveur de l'hôte — la ligne ExtServers le dit. Les deux défauts de la partie 8 tombent ensemble : on désigne les conteneurs par leur nom, et le nom ne change pas au redémarrage.

10Attribuer une adresse fixe — et voir ce qui est refusé

17Deux adresses choisies, et deux refus Moyen

4.1 à 4.4 Lancer deux conteneurs avec une adresse choisie. Essayer ensuite une adresse déjà prise, puis une adresse imposée sur le réseau bridge par défaut. Nettoyer.

✅ Correction
bash
docker run -dit --name client --network reseau-lan --ip 192.168.50.10 alpine
docker run -dit --name poste2 --network reseau-lan --ip 192.168.50.20 alpine

Les deux obtiennent exactement l'adresse demandée. Les deux essais suivants échouent, et pas pour la même raison :

bash
docker run -dit --name doublon --network reseau-lan --ip 192.168.50.10 alpine
sortie
docker: Error response from daemon: failed to set up container networking:
Address already in use
bash
docker run -dit --name test-ip --ip 172.17.0.50 alpine
sortie
docker: Error response from daemon: invalid config for network bridge:
invalid endpoint settings: user-specified IP address is supported on
user-defined networks only

Le premier refus est un conflit : l'adresse est prise. Le second est un interdit : le réseau par défaut ne gère pas les adresses imposées, quelle que soit l'adresse. D'où la déduction attendue — une adresse fixe n'est possible que sur un réseau qu'on a créé soi-même.

🛠️ Pourquoi le sujet demande de nettoyer en 4.4

Parce que les deux essais ne laissent pas la même trace. doublon a été créé puis a échoué au démarrage : il apparaît à l'état Created et il occupe le nom. test-ip, lui, a été refusé avant d'exister — docker rm -f test-ip n'a donc rien à supprimer, et ne s'en plaint pas.

bash
docker ps -a --format '{{.Names}} | {{.Status}}'
sortie
doublon | Created
poste2 | Up Less than a second
client | Up Less than a second

Un conteneur à l'état Created est un piège discret : il ne tourne pas, il n'apparaît pas dans docker ps sans -a, et pourtant il réserve son nom. Le prochain run --name doublon échouera sans qu'on comprenne pourquoi.

Question 4.1 — fixe ou nom ? L'adresse fixe sert quand quelque chose d'extérieur à Docker doit viser le conteneur : une règle de pare-feu, une route, un équipement qui ne fait pas de DNS, un fichier de configuration qu'on ne maîtrise pas. Partout ailleurs, le nom suffit et vaut mieux : il survit au redémarrage, il ne crée pas de conflit, et il n'oblige pas à tenir un plan d'adressage à jour.

11Isoler deux réseaux, et essayer de les relier

18Deux réseaux, aucun chemin entre eux Moyen

5.1 et 5.2 Créer un second réseau pour la zone serveurs, y lancer un serveur web, puis tenter de le joindre depuis client, qui est resté sur le réseau des postes.

✅ Correction
bash
docker network create --subnet 172.16.10.0/24 reseau-srv
docker run -d --name serveur --network reseau-srv --ip 172.16.10.5 nginx
docker exec client ping -c 2 -W 2 172.16.10.5
sortie
PING 172.16.10.5 (172.16.10.5): 56 data bytes

--- 172.16.10.5 ping statistics ---
2 packets transmitted, 0 packets received, 100% packet loss

Question 5.1 — pourquoi ça ne passe pas. Parce qu'être sur la même machine ne veut rien dire pour un réseau. Chaque réseau Docker est un commutateur virtuel séparé, et client n'a qu'une patte, sur reseau-lan. Pour atteindre 172.16.10.5, il lui faudrait une route vers ce réseau et quelqu'un pour router : il n'a ni l'une ni l'autre. Son seul chemin par défaut mène à la passerelle de son propre réseau, qui ne relaie pas vers l'autre.

C'est exactement la situation de deux VLAN sans routeur entre eux. Le câble existe, l'isolation aussi.

19Un conteneur à deux pattes ne route pas Difficile

5.3 à 5.5 Créer un conteneur raccordé aux deux réseaux, compter ses interfaces, tester les deux côtés depuis lui, puis refaire le test de 5.2 depuis client.

✅ Correction
bash
docker run -dit --name passerelle --network reseau-lan alpine
docker network connect reseau-srv passerelle
docker exec passerelle ip -4 -o addr show
sortie
1: lo      inet 127.0.0.1/8 scope host lo
11: eth0   inet 192.168.50.3/24 brd 192.168.50.255 scope global eth0
12: eth1   inet 172.16.10.2/24 brd 172.16.10.255 scope global eth1

Deux interfaces, une par réseau : eth0 dans reseau-lan, eth1 dans reseau-srv. Et elle joint les deux côtés, par leur nom :

sortie
$ docker exec passerelle ping -c 2 client
64 bytes from 192.168.50.10: seq=0 ttl=64 time=0.115 ms

$ docker exec passerelle ping -c 2 serveur
64 bytes from 172.16.10.5: seq=0 ttl=64 time=0.195 ms

Et pourtant, client ne joint toujours pas serveur : 100 % de perte, exactement comme avant.

⚠️ Question 5.2 — pourquoi la passerelle ne sert à rien, ici

Un conteneur raccordé à deux réseaux parle aux deux. Il ne transmet pas pour autant : router n'est pas la même chose que joindre. Il manque deux choses, et il faut les deux.

Sur la passerelle : activer le transfert de paquets (net.ipv4.ip_forward), ce qui demande en plus un conteneur privilégié — par défaut, un conteneur n'a pas le droit de toucher à ce réglage du noyau.

Sur le client : une route disant que 172.16.10.0/24 se joint via 192.168.50.3. Sans elle, le client envoie vers sa passerelle par défaut, qui n'est pas ce conteneur.

Et même avec les deux, il resterait le retour : serveur devrait savoir revenir vers 192.168.50.0/24. Une route n'est jamais à sens unique — c'est la faute la plus classique du TP de routage, et elle se retrouve ici à l'identique.

20Le raccordement direct, et ce qu'il coûte Moyen

5.6 Raccorder client directement au réseau des serveurs, tester, puis l'en retirer et retester.

✅ Correction
bash
docker network connect reseau-srv client
docker exec client ping -c 2 serveur
docker network disconnect reseau-srv client
docker exec client ping -c 2 -W 2 serveur
sortie
64 bytes from 172.16.10.5: seq=0 ttl=64 time=0.096 ms
2 packets transmitted, 2 packets received, 0% packet loss

ping: bad address 'serveur'

Raccordé, il joint. Débranché, le nom lui-même cesse d'exister — pas « 100 % de perte », mais bad address. Le résolveur interne ne répond que pour les réseaux auxquels on appartient : on perd la résolution avant même de perdre le chemin.

Question 5.3 — pourquoi c'est une mauvaise idée. Parce que ça marche. C'est précisément le danger : la solution qui fonctionne tout de suite est celle qui supprime la segmentation. Relier le poste utilisateur au réseau des serveurs, c'est lui donner accès à tout ce qui s'y trouve, pas au seul service dont il a besoin — et le jour où ce poste est compromis, l'attaquant hérite du même accès. La segmentation ne sert à rien si on la perce dès qu'elle gêne ; ce qu'on veut, c'est un point de passage contrôlé, pas un fil direct.

12Réseau interne et ports publiés : deux mondes

21Pourquoi « -p » ne sert pas à ce qu'on croit Moyen

6.1 à 6.3 Récupérer la page du serveur depuis un conteneur du même réseau, sans aucun port publié. Tenter la même chose depuis le navigateur de la machine, puis relancer un serveur avec -p 8080:80.

✅ Correction
bash
docker exec passerelle wget -qO- http://serveur | head -5
sortie
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>

Aucune option -p n'a été donnée à serveur, et pourtant la page arrive. Depuis le navigateur de la machine, au contraire :

bash
curl -s -m 3 http://localhost:8080
sortie
curl: (7) Failed to connect to localhost port 8080: Connection refused

Après publication du port, la même adresse répond :

bash
docker run -d --name serveur-pub --network reseau-srv -p 8080:80 nginx
curl -s http://localhost:8080 | head -4
docker port serveur-pub
sortie
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>

80/tcp -> 0.0.0.0:8080
80/tcp -> [::]:8080

Question 6.1 — la seule phrase à retenir : -p ne sert jamais à faire communiquer deux conteneurs. Il sert à percer la frontière entre la machine et le réseau Docker. passerelle est dans reseau-srv : elle est déjà du bon côté. Le navigateur, lui, est dehors — pour lui, le port 80 du conteneur n'existe pas tant qu'on ne l'a pas publié sur un port de la machine.

⚠️ Question 6.2 — ce que docker port avoue

La sortie dit 0.0.0.0:8080, pas 127.0.0.1:8080 : toutes les interfaces de la machine, y compris celle du Wi-Fi. Un service qu'on croyait « en local » est en réalité offert à tout le réseau de la salle, du logement ou de l'entreprise.

Un service joignable seulement depuis un réseau Docker interne a une surface d'attaque qui s'arrête aux conteneurs du même réseau. Publié, il s'expose à tout ce qui peut atteindre la machine. Si la publication est nécessaire, elle se limite à l'interface de bouclage : -p 127.0.0.1:8080:80.

La règle : on publie ce qui doit être public — une façade web — et on laisse tout le reste (bases de données, caches, files) sur le réseau interne. Une base publiée par confort est la fuite de données de l'année suivante.

13Nettoyage — et l'ordre qui compte

22Pourquoi les conteneurs avant les réseaux Facile

7.1 et 7.2 Lister ce qui existe, puis tout supprimer. Essayer d'abord de supprimer un réseau sans toucher aux conteneurs : la réponse de Docker est la correction de la question.

✅ Correction
bash
docker network rm reseau-lan
sortie
Error response from daemon: error while removing network: network
reseau-lan has active endpoints (name:"poste2" ..., name:"passerelle" ...,
name:"c1" ..., name:"c2" ..., name:"client" ...)

Voilà la réponse, donnée par Docker lui-même : un réseau qui porte encore des extrémités actives ne se supprime pas. Chaque conteneur raccordé y possède un point d'attache ; supprimer le réseau sous leurs pieds laisserait des conteneurs branchés sur rien. Dans l'ordre, donc :

bash
docker rm -f c1 c2 client poste2 passerelle serveur serveur-pub
docker network rm reseau-lan reseau-srv
docker ps -a
docker network ls
sortie
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES

NETWORK ID     NAME      DRIVER    SCOPE
6bca5b86a5e3   bridge    bridge    local
a647ec3084f5   host      host      local
b002c30b9b60   none      null      local

Plus aucun conteneur, et les trois réseaux livrés avec Docker — exactement l'état du début. C'est le contrôle qui termine le TP : on ne range pas « à peu près ».

🛠️ Les images, elles, restent

docker rm supprime des conteneurs, pas des images. alpine et nginx occupent encore le disque, et c'est voulu : les re-télécharger à chaque TP serait absurde. Pour les enlever quand même, c'est docker rmi alpine nginx — et docker system df dit ce que tout cela pèse.

Sources exploitées

  • « TP d'initiation — Manipuler Docker », Réseaux & Systèmes : six parties, vingt-cinq questions numérotées, et son aide-mémoire de commandes
  • « TP — Le réseau avec Docker », Systèmes & Réseaux : sept parties, douze questions de cours, cinq schémas d'architecture
  • Documentation officielle docs.docker.com — messages d'erreur et comportements vérifiés le 9 octobre 2026

Les sorties reproduites ici sont celles d'une installation neuve. Les identifiants d'images et leurs tailles changent d'un jour à l'autre : ce sont des images publiques qui évoluent, et aucune des réponses de ces TP ne repose sur ces valeurs-là.

Les sorties des parties 7 à 13 ont été obtenues en exécutant le TP, le 15 octobre 2026, du premier docker network ls au nettoyage final : adresses, messages d'erreur et temps de réponse sont ceux qu'une machine a rendus, pas ceux qu'une documentation annonce. Deux écarts avec l'énoncé ont été relevés à cette occasion, et sont expliqués là où ils apparaissent — l'adresse distribuée en premier (partie 9) et la trace laissée par les deux essais refusés (partie 10). Une sortie recopiée n'est pas une sortie vérifiée.