Commandes de base Docker

Docker est un outil puissant pour créer, déployer et exécuter des applications dans des conteneurs. Voici un guide pratique des commandes de base et des concepts fréquemment utilisés.


Gestion des conteneurs

1. Lancer un conteneur avec une commande

Pour exécuter un conteneur basé sur l'image Alpine et lui fournir une commande :

docker container run alpine echo hello

2. Lancer un conteneur interactif

Pour ouvrir un shell interactif sur un conteneur Alpine :

docker container run -ti alpine

3. Publier des ports

Pour exécuter un conteneur basé sur Nginx et mapper le port 80 du conteneur sur le port 8080 de l'hôte :

docker container run -d -p 8080:80 nginx

Inspection des conteneurs

4. Lister les conteneurs

Conteneurs actifs uniquement :

docker container ls
ou
docker ps

Conteneurs actifs et stoppés :

docker container ls -a
ou
docker ps -a

Afficher les identifiants uniquement :

docker container ls -aq
ou
docker ps -aq

5. Inspecter un conteneur

Pour inspecter un conteneur spécifique :

docker container run -d -p 8000:80 nginx:1.20
docker inspect <CONTAINER_ID>

Pour récupérer l'adresse IP du conteneur:

docker inspect -f '{{ .NetworkSettings.IPAddress }}' <CONTAINER_ID>

6. Suivre les logs

Pour suivre les journaux d'un conteneur en temps réel :

docker container logs -f <CONTAINER_ID>

7. Exécuter un shell dans un conteneur

Pour accéder à un shell sh dans un conteneur nommé ping :

docker exec -ti ping sh
/ # ps aux

Gestion des conteneurs actifs ou stoppés

8. Arrêter tous les conteneurs actifs

docker container stop $(docker container ls -q)

9. Supprimer tous les conteneurs stoppés

docker container rm $(docker container ls -aq)

Gestion des images

10. Télécharger une image

Pour télécharger une image MongoDB version 3.6 :

docker image pull mongo:3.6

11. Inspecter une image

Pour voir l'historique des couches de construction :

docker image history mongo:3.6

Pour voir les informations complètes :

docker image inspect mongo:3.6

Pour visualiser les ports exposés par l'image :

docker image inspect --format '{{ json .ContainerConfig.ExposedPorts }}' mongo:3.6

12. Exporter et supprimer une image

Pour exporter une image dans un fichier tar :

docker save -o mongo-3.6.tar mongo:3.6

Pour supprimer une image :

docker image rm mongo:3.6

ENTRYPOINT vs CMD

ENTRYPOINT et CMD sont deux instructions clés dans un Dockerfile pour définir comment un conteneur doit être exécuté. Bien qu'elles aient des similarités, elles diffèrent dans leur comportement et leur flexibilité. :

1. CMD : commande par défaut

  • Utilisé pour spécifier une commande par défaut à exécuter lorsque le conteneur démarre.
  • Peut être remplacée lors de l'exécution du conteneur.
Exemple Dockerfile :
FROM alpine
CMD ["echo", "Hello, World!"]
Lors de l'exécution :
docker container run alpine
# Output: Hello, World!

docker container run alpine echo "Custom Message"
# Output: Custom Message

ENTRYPOINT : commande fixe

  • Définit une commande qui sera toujours exécutée par le conteneur.
  • Les arguments peuvent être ajoutés au moment de l'exécution, mais la commande elle-même ne peut pas être remplacée facilement.
Exemple Dockerfile :
FROM alpine
ENTRYPOINT ["echo"]

Lors de l'exécution :

docker container run alpine "Hello, ENTRYPOINT!"
# Output: Hello, ENTRYPOINT!

Utilisation combinée

  • ENTRYPOINT définit la commande principale.
  • CMD fournit les arguments par défaut qui peuvent être remplacés.
Exemple Dockerfile :
FROM alpine
ENTRYPOINT ["ping"]
CMD ["-c3", "localhost"]

Lors de l'execution :

docker container run alpine
# Ping localhost 3 fois

docker container run alpine 8.8.8.8
# Ping 8.8.8.8 avec l'option -c3

Dans cet exemple, Docker sait que 8.8.8.8 remplace localhost et pas -c3 car :

  1. Les arguments fournis au moment de l'exécution (8.8.8.8) sont ajoutés après ceux définis dans CMD.
  2. Le comportement par défaut est que CMD agit comme une liste d'arguments qui s'ajoute à la commande spécifiée par ENTRYPOINT.
  3. Ainsi, ENTRYPOINT (ping) reste inchangé, et les arguments CMD sont complétés ou remplacés suivant leur ordre : ici 8.8.8.8 remplace localhost, mais -c3 reste intact.

Modifier ENTRYPOINT à l'exécution

Pour remplacer une commande d'ENTRYPOINT, utilisez l'option --entrypoint :

docker container run --entrypoint ls alpine
# Liste les fichiers dans l'image alpine
Récapitulatif : ENTRYPOINT vs CMD
Récapitulatif : ENTRYPOINT vs CMD
Propriété ENTRYPOINT CMD
Priorité Toujours exécutée Remplaçable à l'exécution
Arguments possibles Oui, ajoutés à l'exécution Oui, peut être remplacé complètement
Exemple de scénario Exécuter un programme fixe Spécifier une commande par défaut

Construction Multi-Stage avec Flask

Structure du projet

.
├── app.py
├── requirements.txt

Contenu de app.py :

from flask import Flask

app = Flask(__name__)

@app.route("/")
def home():
    return "Hello, Multi-Stage Build with Python!"

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000)

Contenu de requirements.txt :

Flask==2.3.2

Dockerfile Multi-Stage Build

<!-- Étape 1 : installer les dépendances -->
FROM python:3.10-alpine AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Étape 2 : image minimale
FROM python:3.10-alpine
WORKDIR /app
COPY --from=builder /usr/local/lib/python3.10/site-packages /usr/local/lib/python3.10/site-packages
COPY --from=builder /usr/local/bin /usr/local/bin
COPY app.py .
EXPOSE 5000
CMD ["python", "app.py"]

Construction et exécution

1. Construire l'image Docker :

docker build -t python-multi-stage .

2. Exécuter le conteneur :

docker run --rm -p 5000:5000 python-multi-stage

3. Accéder à l'application : http://localhost:5000

Explications

  1. Préparer les dépendances
    • Utilise une image Python complète (python:3.10-slim) pour installer les dépendances listées dans requirements.txt.
    • Les dépendances sont installées dans /usr/local/lib/python3.10/site-packages.
  2. Image finale
    • Utilise une image Alpine légère pour exécuter l'application.
    • Copie uniquement les dépendances installées depuis l'étape précédente.
    • Ajoute le fichier app.py à l'image.
  3. Ce qui est réellement important:
    • La syntaxe clé : FROM AS — AS crée un alias pour l’étape de build.
    • On utilise ensuite cet alias pour récupérer des artefacts : COPY --from=buider .

Pourquoi utiliser un Multi-Stage Build ici ?

  • L'image finale avec Alpine est beaucoup plus petite que l'image Python complète.
  • Performance : Seules les dépendances nécessaires sont incluses (pas de caches de pip ou de fichiers inutiles).
  • Sécurité : Réduit la surface d'attaque en utilisant une image minimale.

2) Les volumes

Les données créées dans un conteneur ne sont pas persistantes par défaut.
Les volumes permettent de persister des données en dehors de l’UnionFS du conteneur.

Prérequis

  • Installation de jq

Pour certaines commandes, l'utilitaire jq est utile pour manipuler du JSON. Voir stedolan.github.io/jq/download/.

Définition d'un volume dans un Dockerfile

Nous allons maintenant voir comment les volumes sont utilisés pour permettre de persister des données en dehors d’un container.
Nous allons commencer par créer un Dockerfile basé sur l’image alpine et définir /data en tant que volume.

FROM alpine:3.17
VOLUME ["/data"]

On peut Utiliser la commande inspect pour récupérer la clé Mounts afin d’avoir le chemin d’accès du volume sur la machine hôte.

docker container inspect -f "{{ json .Mounts }}" c2 | jq .

Cela donne un résultat similaire à celui ci-dessous (aux ID prêts) :

[
  {
    "Type": "volume",
    "Name": "d071337...3896",
    "Source": "/var/lib/docker/volumes/d07133...3896/_data",
    "Destination": "/data",
    "Driver": "local",
    "Mode": "",
    "RW": true,
    "Propagation": ""
  }
]

Exemple de manipulation :

Construisons l'image imgvol à partir de ce Dockerfile.

docker image build -t imgvol .

Avec la commande suivante, lançons un shell intéractif dans un container, nommé c2, basé sur l’image imgvol.

docker container run --name c2 -ti imgvol

Depuis le container, créons le fichier /data/hello.txt

touch /data/hello.txt

Sortons ensuite du container avec la commande CTRL-P suivie de CTRL-Q, cette commande permet de passer le process en tache de fond, elle n'arrête pas le container.
Pour être sur que c2 tourne, il doit apparaitre dans la liste des containers en execution. Vérifions le avec la commande suivante:

docker container ls

Le volume /data est accessible, sur la machine hôte, dans le path spécifié par la clé Source.
Avec les commandes suivantes, vérifions que le fichier hello.txt est bien présent sur la machine hôte.



VOLUME_PATH=$(docker container inspect -f "{{ (index .Mounts 0).Source }}" c2)

find $VOLUME_PATH -name hello.txt

Supprimons maintenant le container c2.



docker container stop c2 && docker container rm c2

Vérifions que le fichier hello.txt existe toujours sur le filesystem de l’hôte.



find $VOLUME_PATH -name hello.txt

Cet exemple nous montre qu’un volume permet de persister les données en dehors de l’union filesystem et ceci indépendamment du cycle de vie d’un container.

Définition d’un volume au lancement d’un container

Précédemment nous avons défini un volume dans le Dockerfile, nous allons maintenant voir comment définir des volumes à l’aide de l’option -v au lancement d’un container.
Pour ce faire, lançons la commande suivante:

docker container run --name c3 -d -v /data alpine:3.17 sh -c 'ping 8.8.8.8 > /data/ping.txt'

Inspectons le container et repérons notamment le chemin d’accès du volume sur la machine hôte.

$ docker inspect -f "{{ json .Mounts }}" c3 | jq .
[
  {
    "Type": "volume",
    "Name": "2ba36b...3ef2",
    "Source": "/var/lib/docker/volumes/2ba36b...3ef2/_data",
    "Destination": "/data",
    "Driver": "local",
    "Mode": "",
    "RW": true,
    "Propagation": ""
  }
]

Le volume est accessible via le filesystem de la machine hôte dans le path spécifié par la clé Source.
En utilisant les commandes suivantes, vérifions que le fichier ping.txt est mis à jour régulièrement par la commande qui tourne dans le container:

VOLUME_PATH=$(docker container inspect -f "{{ (index .Mounts 0).Source }}" c3)
tail -f $VOLUME_PATH/ping.txt

Si nous stoppons et supprimons le container, le fichier ping.txt sera toujours disponible via le volume, cependant il ne sera plus mis à jour.
Supprimons le container c3 avec la commande suivante.

docker container rm -f c3

Utilisation des volumes via la CLI

Les commandes relatives aux volumes permettent de manager le cycle de vie des volumes de manière très simple.
La commande suivante liste l’ensemble des sous-commandes disponibles.

docker volume --help

La commande create permet de créer un nouveau volume. Créons un volume nommé html avec la commande suivante.

docker volume create --name html

Avec la commande suivante, listons les volumes existants et vérifions que le volume html est présent.

docker volume ls

Comme pour les containers et les images (et d’autres primitives Docker), la commande inspect permet d’avoir la vue détaillée d’un volume. Inspectez le volume html

docker volume inspect my_volume
[
    {
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/html/_data",
        "Name": "html",
        "Options": {},
        "Scope": "local"
    }
]

3) Vue d'ensemble des networks

Dans cette mise en pratique, nous allons étudier la mise en réseau des containers Docker.
Nous verrons notamment les networks qui sont crées par défaut lors de l’installation de la plateforme et nous passerons en revue différents drivers qui sont disponibles avec une installation standard.

1. Les networks créés par défaut

Lors de l’installation de la plateforme Docker, plusieurs networks sont créés.
Listons les networks avec la commande suivante:

docker volume inspect my_volume
$ docker network ls

NETWORK ID          NAME                DRIVER              SCOPE
78600647cf6b        bridge              bridge              local
f68b62cc1152        host                host                local
5a4a4afa414b        none                null                local
]

Lorsqu’un container est créé, nous pourrons spécifier le network auquel il sera attaché:

  • un container attaché au network none n’aura pas de connectivité externe. Cela peut-être utile par exemple pour un container servant pour du debug
  • un container attaché au network host bénéficiera de la stack network de la machine hôte
  • un container attaché au network bridge pourra communiquer avec les autres containers attaché à ce network.
    Il faut cependant noter que ce type de network permet seulement une connectivité entre containers tournant sur la même machine
Avec la commande suivante, listons les interfaces de la machine hôte:

$ ip a
On obtiendra un résultat similaire à celui ci-dessous (les noms de certaines interfaces pourront être différentes):
1: lo:  mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: enp0s3:  mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 02:4d:82:c4:d5:87 brd ff:ff:ff:ff:ff:ff
    inet 10.0.2.15/24 brd 10.0.2.255 scope global dynamic enp0s3
       valid_lft 85937sec preferred_lft 85937sec
    inet6 fe80::4d:82ff:fec4:d587/64 scope link
       valid_lft forever preferred_lft forever
3: enp0s8:  mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 08:00:27:68:30:03 brd ff:ff:ff:ff:ff:ff
    inet 192.168.33.10/24 brd 192.168.33.255 scope global enp0s8
       valid_lft forever preferred_lft forever
    inet6 fe80::a00:27ff:fe68:3003/64 scope link
       valid_lft forever preferred_lft forever
4: docker0:  mtu 1500 qdisc noqueue state DOWN group default
    link/ether 02:42:ac:d1:34:9b brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever

La chose importante à noter ici est l’existance d’une interface nommée docker0.
C'est un bridge Linux qui a été créée lors de l’installation de la plateforme.

1.1. Lancement d’un container en utilisant le driver bridge

Par défaut, si l’option –-network n’est pas spécifiée au lancement d’un container, celui-ci est attaché au network nommé bridge.
Utilisons la commande suivante pour lancer un container nommé c1 basé sur l’image alpine.

 $ docker container run -d --name c1 alpine:3.8 sleep 10000
 

Note: nous spécifions sleep 10000 dans la commande de façon à ce que le processus de PID 1 de ce container tourne quelques temps.
Avec la commande suivante, récupérons la configuration réseau de ce container (nous utilisons la notation Go template pour récupérer directement le champ qui nous intéresse (.NetworkSetings.Networks).

 $ docker container inspect -f "{{ json .NetworkSettings.Networks }}" c1 | jq .
 

La sortie de cette commande nous donne l’ID du network auquel ce container est attaché, via la clé NetworkID.
Nous pouvons alors voir que cette valeur correspond à l’ID du network bridge.
Nous obtenons également d’autres information comme l’IP du container et celle de la passerelle.

 {
    "bridge": {
        "Aliases": null,
        "DriverOpts": null,
        "EndpointID": "1968a1143dba15cad49dc84b06839b83e42d249a5ca6a83c06092840ad205364",
        "Gateway": "172.17.0.1",
        "GlobalIPv6Address": "",
        "GlobalIPv6PrefixLen": 0,
        "IPAMConfig": null,
        "IPAddress": "172.17.0.2",
        "IPPrefixLen": 16,
        "IPv6Gateway": "",
        "Links": null,
        "MacAddress": "02:42:ac:11:00:02",
        "NetworkID": "78600647cf6b67dbe6fcc0dcc9b06a59a0b5c36033fa088c030490959901ee16"
    }
}
 

Avec la commande suivante, inspectons le network bridge.
Note: cette commande utilise le formalisme Go template pour extraire la clé Containers afin d’obtenir la liste des containers attachés à ce network.

$ docker network inspect -f "{{ json .Containers }}" bridge | jq .

Vous devriez obtenir un résultat proche de celui ci-dessous, dans lequel le container c1 est listé. On voit donc que c1 est attaché au network.

{
    "3f41f1295700be13435e82df1e98f1575f4380cfdcb8b315e4b275485e4c2470": {
        "EndpointID": "1968a1143dba15cad49dc84b06839b83e42d249a5ca6a83c06092840ad205364",
        "IPv4Address": "172.17.0.2/16",
        "IPv6Address": "",
        "MacAddress": "02:42:ac:11:00:02",
        "Name": "c1"
    }
}

Supprimons le container c1 .

$ docker rm -f c1

1.2. Lancement d’un container en utilisant le driver host

Avec la commande suivante, listons les interfaces réseau existantes sur la machine hôte.

 $ ip link show
 

Vous devriez obtenir un résultat proche de celui ci-dessous.

1: lo:  mtu 65536 qdisc noqueue state UNKNOWN qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: docker0:  mtu 1500 qdisc noqueue state DOWN
    link/ether 02:42:4a:a3:69:00 brd ff:ff:ff:ff:ff:ff
50947: eth0@if50948:  mtu 1450 qdisc noqueue state UP
    link/ether 02:42:0a:00:a7:03 brd ff:ff:ff:ff:ff:ff
50949: eth1@if50950:  mtu 1500 qdisc noqueue state UP
    link/ether 02:42:ac:12:00:9d brd ff:ff:ff:ff:ff:ff
 

Lancez alors un shell interactif dans un container basé sur l’image alpine.
Spécifiez l’option –-network=host de façon à ce que ce container utilise la stack network de la machine hôte.

$ docker container run -ti --name c1 --network=host alpine:3.8 sh

Une fois dans le container, listez les interfaces réseau.

 $ ip link show
 

La liste des interfaces devrait être la même que celle obtenue directement depuis la machine hôte.
Sortez du container et supprimez le.

# exit
$ docker rm c1
 

1.3. Lancement d’un container en utilisant le driver none

Lancez à présent un shell interactif dans un container basé sur l’image alpine mais cette fois en utilisant l’option –-network=none de façon à ne pas donner au container de connectivité vers l’extérieur.

$ docker container run -ti --name c1 --network none alpine:3.8 sh

Listez les interfaces réseau du container.

# ip a show

Vous devriez constater que seule lo (l’interface locale) est disponible. .

1: lo:  mtu 65536 qdisc noqueue state UNKNOWN qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever

Depuis ce container, essayez de lancer un ping sur le DNS de Google (8.8.8.8).

# ping 8.8.8.8

Vous devriez obtenir le message d'erreur suivant, car ce container n'a pas de connection avec l’extérieur.

PING 8.8.8.8 (8.8.8.8): 56 data bytes
ping: sendto: Network unreachable

Sortez du container et supprimez le.

 # exit
$ docker rm c1
 

2. Bridge network

Le réseau bridge créé par défaut permet à des containers tournant sur la même machine de communiquer entre eux comme nous allons le voir maintenant.

Dans cette partie, nous allons lancer 2 containers, chacun étant attaché au network bridge.
Il n’est pas nécessaire de spécifier l’option –-network car un container est attaché à ce network par défaut.
Utilisez la commande suivante pour lancer un premier container, nommé c1.

$ docker container run -d --name c1 alpine:3.8 sleep 10000

Avec la commande suivante, récupérer l'adresse IP du container.

$ docker container inspect -f "{{ .NetworkSettings.IPAddress }}" c1

Exemple de retour de cette commande:

172.17.0.2

Note: l’adresse IP obtenue sur votre environnement pourra être différente.

Utilisez la commande suivante pour lancer un shell interactif dans un second container basé sur l’image alpine, et nommé c2.

$ docker container run -ti --name c2 alpine:3.8 sh

Depuis ce shell, essayez de lancer un ping sur le container c1 en utilisant l’adresse IP de ce dernier.

Note: lancez la commande suivante en utilisant l’IP du container c1 obtenue précédemment.

 # ping -c 3 172.17.0.2
PING 172.17.0.2 (172.17.0.2): 56 data bytes
64 bytes from 172.17.0.2: seq=0 ttl=64 time=0.135 ms
64 bytes from 172.17.0.2: seq=1 ttl=64 time=0.098 ms
64 bytes from 172.17.0.2: seq=2 ttl=64 time=0.110 ms
--- 172.17.0.2 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.098/0.114/0.135 ms
 

Cette commande fonctionne correctement, le container c2 est capable de pinguer le container c1 en utilisant l’adresse IP de ce dernier.

Toujours depuis le shell obtenu dans le container c2,
lancez un ping vers c1 mais cette fois-ci en utilisant le nom c1 au lieu de son adresse IP.

# ping -c 3 c1

Vous devriez obtenir le message suivant:

ping: bad address 'c1'

Il n’est pas possible, pour des containers attachés au network bridge de communiquer via leur nom.
Nous verrons que cela est par contre possible si nous définissons notre propre network de type bridge.

Sortez du container c2 et supprimez c1 et c2.

# exit
$ docker rm -f c1 c2

3. User defined bridge network

Nous avons vu que, par défaut, 3 networks sont définis (ceux-ci sont nommés bridge, host et none).
Il est également possible d’en créer d’autres, c’est ce que nous allons voir dans les sections suivantes.

Utilisez la commande suivante afin de créer un nouveau network de type bridge nommé bnet.

$ docker network create --driver bridge bnet

Note: nous spécifions ici l’option –-driver bridge pour l’illustration même si ce n’est pas nécessaire car c’est le driver utilisé par défaut.

Utilisez la commande suivante pour lancer un container, nommé c1 et attaché au network bnet.

$ docker container run -d --name c1 --network bnet alpine:3.8 sleep 10000

Comme précédemment, recupérez l’adresse IP de ce container.

Note: cette fois, nous utilisons la clé .NetworkSettings.Networks.bnet.IPAddress afin de récupérer l’IP alouée sur le réseau bnet..

$ docker container inspect -f "{{ json .NetworkSettings.Networks.bnet.IPAddress }}" c1

Exemple de résultat obtenu:

172.18.0.2

Note: l’adresse IP obtenue sur votre environnement peux être différente.

ancez un shell interactif dans un second container, nommé c2 , et également attaché au network bnet.

$ docker container run -ti --name c2 --network bnet alpine:3.8 sh

Depuis c2, lancez un ping sur l'adress IP de c1.

# ping -c 3 172.18.0.2

Vous devriez obtenir un résultat similaire à celui ci-dessous:

 ING 172.18.0.2 (172.18.0.2): 56 data bytes
64 bytes from 172.18.0.2: seq=0 ttl=64 time=1.557 ms
64 bytes from 172.18.0.2: seq=1 ttl=64 time=0.093 ms
64 bytes from 172.18.0.2: seq=2 ttl=64 time=0.088 ms
--- 172.18.0.2 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.088/0.579/1.557 ms
 

Nous pouvons constater que, comme c’est le cas pour le network bridge par défaut, les containers attachés au network bnet peuvent communiquer via leur adresse IP.

Comme précédemment, essayez de lancer un ping sur C1 en utilisant le nom du container au lieu de son adresse IP.

# ping -c 3 c1

Vous devriez obtenir un résultat similaire à celui ci-dessous:

PING c1 (172.18.0.2): 56 data bytes
64 bytes from 172.18.0.2: seq=0 ttl=64 time=0.096 ms
64 bytes from 172.18.0.2: seq=1 ttl=64 time=0.095 ms
64 bytes from 172.18.0.2: seq=2 ttl=64 time=0.089 ms
--- c1 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.089/0.093/0.096 ms

Nous pouvons constater que, contrairement au cas du network bridge par défaut, les containers attachés au network bnet peuvent communiquer via leur nom, il y a une résolution de noms qui s'opère via un serveur DNS embarqué.

Sortez du container c2 et supprimez c1 et c2.

# exit
$ docker rm -f c1 c2


Notes & bonnes pratiques

  • Pour la persistance des données (bases, fichiers users), utiliser des volumes ou monteux d’hôte.
  • Les multi-stage builds réduisent la taille de l'image et augmentent la sécurité.
  • Utilisez des fichiers Dockerfile et des scripts d'entrée (`ENTRYPOINT` / `CMD`) adaptés au comportement souhaité en production.
  • Pensez à versionner vos images et à utiliser un registre (ECR, Docker Hub, etc.) pour déployer dans un pipeline CI/CD.

Si tu veux, je peux : convertir un autre fichier du même dossier dans le même style, ou générer une version Markdown révisée. Dis-moi lequel.