# Formation Docker

Ce cours est distribué gratuitement sous licence [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.fr) par Thibaud FRICHET - [formation.tfrichet.fr](https://formation.tfrichet.fr)

**Prérequis :**

- Fondamentaux Linux et ligne de commande,
- Notions réseaux (adressage IP, ports)

# Chapitre 1 - Prise en main

# Qu'est ce que Docker ?

Docker est une plateforme open-source qui permet aux développeurs de construire, déployer et exécuter des **conteneurs**.

Les conteneurs combinent :

- Le code source de l'application
- Les bibliothèques de l'OS
- Les dépendances nécessaires pour exécuter l'application dans n'importe quel environnement.

[![jose-m-alarcon-lQnGMbSD-5M-unsplash.jpg](https://formation.tfrichet.fr/uploads/images/gallery/2025-07/scaled-1680-/EEDAxfaSlb9EzIfQ-jose-m-alarcon-lqngmbsd-5m-unsplash.jpg)](https://formation.tfrichet.fr/uploads/images/gallery/2025-07/EEDAxfaSlb9EzIfQ-jose-m-alarcon-lqngmbsd-5m-unsplash.jpg)

<sub>Photo de [José M. Alarcón](https://unsplash.com/fr/@jalarcon?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash) sur [Unsplash](https://unsplash.com/fr/photos/un-cargo-navigue-sur-le-bleu-de-locean-lQnGMbSD-5M?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash)</sub>

<p class="callout success">La métaphore avec un porte conteneur est intéressante.  
Les conteneurs se ressemblent d'extérieur. Ils possèdent les mêmes dimensions et les mêmes dispositifs d'attache et d'empilement.  
Depuis l'intérieur d'un conteneur fermé, impossible d'accéder à l'extérieur.</p>

Le terme Docker fait généralement référence à Docker Engine, le moteur d'exécution pour la construction et l'exécution des conteneurs.  
Docker fait également référence à la société qui vend la version commerciale de Docker ou au projet open source Docker.

Docker a été créé en 2013 avant de devenir rapidement un incontournable dans le monde du développement logiciel.

### Conteneurisation vs virtualisation

La conteneurisation de Docker est souvent comparée à de la virtualisation.

Le principe de base est le même : l'application conteneurisée est isolée du reste de la machine et ne peut pas sortir du conteneur, tout comme un système d'exploitation virtualisé ne peut pas sortir de sa machine virtuelle.

<p class="callout info">La conteneurisation va plus loin : Le conteneur n'embarque pas de système d'exploitation mais il isole l'application du reste de la machine hôte : processus, système de fichiers, réseau, ressources CPU et RAM : tout est délimité et isolé du reste.</p>

La conteneurisation offre tous les avantages de la virtualisation, notamment l'isolation et l'évolutivité, ainsi que d'autres avantages :

- **Poids**  
    Contrairement aux machines virtuelles, les conteneurs n'embarquent pas le système d'exploitation. La taille d'un conteneur se mesure généralement en Mo (les VM en Go).
- **Portabilité**  
    Les applications conteneurisées peuvent être écrites une seule fois et exécutées n'importe où.   
    Par rapport aux machines virtuelles, les conteneurs sont plus rapides et plus faciles à déployer, déplacer, orchestrer, etc..
- **Efficacité**  
    Avec les conteneurs, les développeurs peuvent exécuter plusieurs copies d'une application sur une même machine, sans atteindre les limites liées à l'utilisation de plusieurs machines virtuelles simultanées.

La différence peut être schématisée ainsi :

<div drawio-diagram="23"><img src="https://formation.tfrichet.fr/uploads/images/drawio/2025-07/9opgNR33zKP6rNp9-drawing-3-1752685014.png" alt=""/></div>

# Installation et hello-world

### Docker Engine et Docker Desktop

Docker est nativement compatible avec Linux. Il peut être installé et piloté en ligne de commande.

Sous Windows, l'utilisation de Docker Desktop est recommandée, **même si elle n'est pas obligatoire**.  
Il est tout à fait possible d'installer Docker dans WSL, sans Docker Desktop.

<p class="callout warning">Pour la suite de ce cours, nous n'utiliserons que la ligne de commande.</p>

### Installation

#### Linux

La documentation officielle propose une procédure d'installation pour la plupart des distributions : [🔗 Install | Docker Docs](https://docs.docker.com/engine/install/)

Un exemple sous Debian :

```bash
# suppression d'anciennes installations
for pkg in docker.io docker-doc docker-compose podman-docker containerd runc; do sudo apt-get remove $pkg; done

# Ajout du repository Docker officiel (clé PGP)
sudo apt-get update
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

# Ajout du repository Docker officiel (source APT)
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update

# Installation des paquets
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

# Démarrage automatique avec systemd (généralement automatique)
sudo systemctl enable docker.service
sudo systemctl enable containerd.service
```

Par défaut, docker n'est pas utilisable depuis un compte non root. La documentation détaille comment rendre accessible le démon Docker : [🔗 Post-installation steps | Docker Docs](https://docs.docker.com/engine/install/linux-postinstall/)

```bash
# Create the docker group.
sudo groupadd docker

# Add your user to the docker group.
sudo usermod -aG docker $USER

# Add without reboot
newgrp docker
```

#### Windows

Docker préconise l'installation de [🔗 Docker Destkop](https://docs.docker.com/desktop/setup/install/windows-install/).

Sous le capot, Docker Desktop installe Docker Engine dans l'environnement virtualisé WSL ou Hyper-V sous Windows.

Il est tout à fait possible d'installer manuellement WSL sous Windows puis de suivre la procédure d'installation de Docker pour un environnement Linux.

#### MacOS

Voir documentation : [🔗 Docker Desktop Mac](https://docs.docker.com/desktop/setup/install/mac-install/)

#### Machine virtuelle Debian

<p class="callout info">En cas de difficulté, une machine virtuelle Debian 13 prête à l'emploi est disponible ci-dessous.</p>

<span style="color: rgb(132, 63, 161);">**[🔗 Machine virtuelle Debian 13](https://formation.tfrichet.fr/books/machine-virtuelle-debian/page/debian-13-trixie)**</span>

### Hello World

Il est possible de tester l'installation avec la commande suivante :

```bash
docker run hello-world
```

[![image.png](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/scaled-1680-/Gqj7jmHKZOwpBzaq-image.png)](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/Gqj7jmHKZOwpBzaq-image.png)

- `hello-world` est une image docker permettant de vérifier son bon fonctionnement.

# Commandes de base

### Exécuter un conteneur : **docker run**

Commande de base :

```bash
docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
```

Le nom de l'image docker à utiliser doit être spécifié avec le paramètre `image`. Quelques exemples :

- Distributions Linux : `debian`, `ubuntu`, `alpine`
- Logiciels et CMS : `wordpress`, `prestashop`
- Langages de programmation : `php`, `python`
- Bases de données : `mysql`, `postgresql`

<p class="callout info">Il existe actuellement 11 millions d'image (août 2025) sur le Docker Hub (que nous approfondirons plus tard).</p>

La commande **docker run** propose de nombreux paramètres dont voici les principaux :

<table border="1" id="bkmrk-param%C3%A8tre-descriptio" style="border-collapse: collapse; width: 100%; height: 376.703px;"><colgroup><col style="width: 18.236%;"></col><col style="width: 40.8909%;"></col><col style="width: 40.8731%;"></col></colgroup><thead><tr style="height: 34.1094px;"><td style="height: 34.1094px;">**Paramètre**</td><td style="height: 34.1094px;">**Description**</td><td style="height: 34.1094px;">**Exemple**</td></tr></thead><tbody><tr style="height: 104.562px;"><td style="height: 104.562px;">`--name`</td><td style="height: 104.562px;">Attribuer un nom au conteneur.  
Si il n'est pas défini, Docker générera un nom aléatoire à partir d'un dictionnaire de mots.</td><td style="height: 104.562px;">```bash
docker run ubuntu --name=test1
docker run ubuntu --name=test2
docker run ubuntu --name=test3
```

</td></tr><tr style="height: 118.547px;"><td style="height: 118.547px;">`-ti`</td><td style="height: 118.547px;">Les paramètres **-t** et **-i** permettent d’ouvrir un terminal interactif dans le conteneur : **-t** alloue un pseudo-terminal, et **-i** garde l’entrée ouverte.</td><td style="height: 118.547px;">```bash
docker run -ti ubuntu
# je suis redirigé dans la console du conteneur
```

</td></tr><tr style="height: 85.375px;"><td style="height: 85.375px;">`-d`, `--detached`</td><td style="height: 85.375px;">Lance le conteneur en arrière-plan, en mode **détaché**.</td><td style="height: 85.375px;">```bash
docker run -d ubuntu
# le conteneur se lance en arrière-plan
```

</td></tr><tr><td>`--rm`</td><td>Supprimer le conteneur lorsqu'il est arrêté.</td><td>```bash
docker run --rm -ti ubuntu
# le conteneur se lance dans la console
# Il sera supprimé après la sorite (exit)
```

</td></tr><tr><td>-e, --env</td><td>Passer une variable d'environnement à l'application conteneurisée</td><td>```bash
docker run -e VAR1=value1
# la variable VAR1 sera définie dans le conteneur
```

</td></tr></tbody></table>

[![image.png](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/scaled-1680-/0LuZNI0npnbQrj4u-image.png)](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/0LuZNI0npnbQrj4u-image.png)

<p class="callout info">Nous approfondirons plus tard d'autres paramètres de la commande **docker run**.</p>

La commande `docker run` se termine par la commande à lancer dans le conteneur ainsi que ses paramètres. L'ajout de cette commande est optionnel, il n'est pas toujours nécessaire de l'indiquer.

Certaines images ne lancent pas automatiquement d'invite de commande `bash`.  
Par exemple, l'image officielle python démarre sur une invite de commande python :

[![image.png](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/scaled-1680-/AAWH7myJcFLUbol9-image.png)](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/AAWH7myJcFLUbol9-image.png)

### Lister les conteneurs : **docker ps**

La commande `docker ps` liste les conteneurs démarrés sur la machine. Le paramètre `--all`, `-a` permet d'afficher les conteneurs stoppés.

```bash
docker ps
docker ps -a
```

[![image.png](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/scaled-1680-/o6Co2Zkvyb9S7sJU-image.png)](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/o6Co2Zkvyb9S7sJU-image.png)

- `test_1` est lancé en mode détaché (`-d`), il tourne en arrière-plan.
- `test_2` est lancé en mode attaché, il s'arrête lorsque je le quitte avec `exit`.

<p class="callout info">Astuce : il est possible de se détacher d'un conteneur sans l'arrêter avec **CTRL+P** puis **CTRL+Q** !</p>

[![image.png](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/scaled-1680-/0pj3R3M6lRsZutYI-image.png)](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/0pj3R3M6lRsZutYI-image.png)

Après avoir pressé **CTRL+P** puis **CTRL+Q**, je suis détaché du conteneur `test_3` qui tourne encore en arrière-plan.

### Arrêt d'un conteneur : **docker stop** et **docker start**

```bash
docker start MON_CONTENEUR  # démarre le conteneur
docker stop MON_CONTENEUR # arrête le conteneur
docker rm MON_CONTENEUR # supprime le conteneur
```

[![image.png](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/scaled-1680-/FeyEQINzSrFMZfcZ-image.png)](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/FeyEQINzSrFMZfcZ-image.png)

### Visualiser les logs : **docker logs**

```bash
docker logs MON_CONTENEUR
docker logs -f MON_CONTENEUR # -f pour --follow, permet de visualiser en direct le log.
```

[![image.png](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/scaled-1680-/MTGjbDrCF64phQPA-image.png)](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/MTGjbDrCF64phQPA-image.png)

La commande date est lancée au démarrage du conteneur détaché, je ne vois pas le résultat.  
J'y accède plus tard avec la commande `docker logs`.

### S'attacher à un conteneur : **docker attach**

```bash
docker attach MON_CONTENEUR
```

[![image.png](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/scaled-1680-/TaP7EyVXDgmLB3Iw-image.png)](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/TaP7EyVXDgmLB3Iw-image.png)

Le conteneur a été lancé en arrière-plan (mode détaché) avec `-d`. Je peux y accéder plus tard.

### Exécuter une commande : **docker exec**

```bash
docker exec [OPTIONS] CONTAINER COMMAND [ARG...]
```

La syntaxe de cette commande ressemble à `docker run`. La différence est qu'ici, la commande est lancée dans un conteneur déjà en cours d'exécution.

La commande `docker exec` est utile pour lancer une invite de commande (`bash`) dans un conteneur qui n'en propose pas forcément. Exemple avec python :

[![image.png](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/scaled-1680-/xJWV0JAyIQMm2b0f-image.png)](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/xJWV0JAyIQMm2b0f-image.png)

Je lance un conteneur python sans préciser de commande. La commande par défaut de l'image docker est utilisée.  
Lorsque je m'attache au conteneur avec `docker attach`, je suis dans une invite de commande python.  
Je me détache du conteneur et je lance une invite de commande `bash` avec `docker exec`.

<p class="callout warning">La commande **docker exec** exécute un nouveau processus dans le conteneur. La sortie de ce processus n'est pas visible dans les logs du conteneur !</p>

# Images et système de fichiers

### Images Docker

Une image Docker est un **fichier** **immuable** qui contient tout le nécessaire pour exécuter une application dans un conteneur.  
Elle contient le code source, les bibliothèques, les dépendances, les variables d'environnement, les configurations, etc.

L'image Docker est une photographie figée d’un environnement logiciel, garantissant que l'application s’exécutera systématiquement de la même manière, indépendamment de son environnement.

Une image Docker est construite à partir d'un fichier de configuration appelé **Dockerfile.** Ce fichier décrit étape par étape comment assembler l'image.

<p class="callout info">Nous reviendrons plus tard sur le Dockerfile.</p>

Une fois construite, une image peut être stockée localement ou poussée vers un registre Docker (comme Docker Hub ou un registre privé), où elle peut être versionnée et partagée.

Pour identifier une image Docker, on utilise la syntaxe suivante :

```
propriétaire/nom:tag
```

- **propriétaire :** correspond à l'utilisateur ou à l'organisation qui possède l'image sur le registre.
- **nom :** le nom de l’image, comme `nginx`, `ubuntu`, etc.
- **tag :** une étiquette qui permet de versionner l'image. Par défaut, si aucun tag n'est spécifié, Docker utilise `latest`.  
    Les tags peuvent représenter des versions (1.0, v2.3.4) ou des environnements (dev, prod).

<p class="callout info">Certaines images "officielles" ne possèdent pas de propriétaire, donc il n'est pas nécessaire de le préciser.</p>

**Quelques exemples :**

```bash
nginx:latest # image officielle de Nginx, version la plus récente.
thibaud/appli:1.2.0 # image personnalisée appartenant à l’utilisateur thibaud, version 1.2.0.
```

### Registry Docker Hub

Comme expliqué ci-dessus, les images Docker doivent être stockées sur un **registre**.

Un registre peut être **public** ou **privé**, par exemple au sein d'une entreprise afin d'y stocker une application propriétaire.

Le registre public et par défaut est le Docker Hub. [🔗 Docker Hub](https://hub.docker.com/search?badges=official&badges=verified_publisher&badges=open_source)

La commande `docker pull` permet de télécharger une image depuis un registre, par défaut le registre public **Docker Hub**.

[![image.png](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/scaled-1680-/0pZ8e1JXEXOVFq4j-image.png)](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/0pZ8e1JXEXOVFq4j-image.png)

Actuellement, l'image `latest` d'Ubuntu correspond à [la version 24.04 sur le Docker Hub](https://hub.docker.com/_/ubuntu).  
Ces trois identifiants retournent la même image, qui n'est téléchargée qu'une seule fois : `ubuntu`, `ubuntu:latest`, `ubuntu:24.04`.

À l'inverse, si je lance un `docker run` sur une image qui n'est pas déjà stockée localement, le téléchargement est automatique depuis le Docker Hub :

[![image.png](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/scaled-1680-/dq7hBWVA66AJdMQs-image.png)](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/dq7hBWVA66AJdMQs-image.png)

La commande `docker image` permet de gérer les images stockées localement.

```bash
docker image ls # lister les images
docker image rm ubutu:18.04 # supprimer l'image ubuntu taggée 18.04
```

[![image.png](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/scaled-1680-/gaB4xpRrpvdJutGM-image.png)](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/gaB4xpRrpvdJutGM-image.png)

### Système de fichiers

L'arborescence des fichiers dans un conteneur Docker est **isolé** du reste de la machine.

Un conteneur Docker contient une arborescence Linux. Exemple ci-dessous :

[![image.png](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/scaled-1680-/TxybcMuoLO2uPg3x-image.png)](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/TxybcMuoLO2uPg3x-image.png)

L'arborescence ci-dessus n'est accessible que depuis le conteneur.

- Le conteneur n'a pas accès à l'arborescence de la machine hôte.
- La machine hôte n'a pas accès à l'arborescence du conteneur.

[![image.png](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/scaled-1680-/sREoYOe8EA55Uaho-image.png)](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/sREoYOe8EA55Uaho-image.png)

### Volatilité

Contrairement à une **image** docker qui est figée, le contenu d'un container est **volatile**.

<p class="callout warning">Lorsqu'un container est supprimé, tous les fichiers modifiés sont perdus !</p>

Exemple :

[![image.png](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/scaled-1680-/UJ9Yu0IiGu97R8Oq-image.png)](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/UJ9Yu0IiGu97R8Oq-image.png)

Les deux conteneurs nommés `test` à partir de l'image `debian` ne sont pas les mêmes : ils n'ont pas le même identifiant.

Les données du premier conteneur sont perdues lors de sa suppression.

<p class="callout info">Nous verrons plus loin les **volumes Docker** qui permettent de stocker des données persistantes.</p>

### Isolation : un exemple avec fastfetch

[![image.png](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/scaled-1680-/1LUeNHgFuW7BHYrZ-image.png)](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/1LUeNHgFuW7BHYrZ-image.png)

<p class="callout success">L'hôte et le conteneur **partagent** quelques éléments tels que le noyau Linux, le CPU, la RAM disponible et l'espace disque.  
À l'inverse, la distribution Linux, les paquets installés et le système de fichier sont **isolés**.</p>

# Les volumes

### Utilisation des volumes

Les volumes sont des répertoires accessibles depuis le conteneur et l'hôte.  
**Les volumes sont persistants**, contrairement aux fichiers des conteneurs docker.

Les volumes sont très utiles avec Docker, ils peuvent servir à stocker, par exemple :

- Les données d'une base de données : il existe des images Docker pour la plupart des SGBD
- Les fichiers de configuration d'une application
- Les fichiers utilisateurs d'une application web (uploads)
- etc.

Une application dockerisée correctement configurée doit stocker ses données dans des volumes.

<p class="callout success">L'avantage des volumes est la sauvegarde : seules les données **utiles** s'y trouvent et il est possible de dupliquer une application à partir d'une image docker et des volumes associés.</p>

Un volume est accessible depuis le container à partir de son point de montage. Exemple :

[![image.png](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/scaled-1680-/f6T663ampOYaXDFW-image.png)](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/f6T663ampOYaXDFW-image.png)

Le dossier `/home/debian/test` de l'hôte est monté dans `/toto` au sein du conteneur avec le paramètre `-v`. L'hôte et le conteneur ont accès au répertoire.

```bash
-v /home/debian/test:/toto
```

Généralement, les images Docker d'applications indiquent les répertoires dans lesquels elles stockent leurs données utiles, par exemple :

- **MariaDB** stocke les fichiers de bases de données dans `/var/lib/mysql` ([documentation](https://hub.docker.com/_/mariadb#where-to-store-data)).
- **Odoo** stocke des données dans `/var/lib/odoo` ([documentation](https://hub.docker.com/_/odoo#use-named-volumes-to-preserve-data)).
- **Redmine** stocke les fichiers uploadés dans `/usr/src/redmine/files` ([documentation](https://hub.docker.com/_/redmine#where-to-store-data)).

### Volumes mappés vs. Volumes managés

Il existe deux types de volumes Docker : mappés et managés. Ils diffèrent par leur mode de gestion et leur usage :

- **Les volumes mappés** relient un dossier de l'hôte à un dossier du conteneur, ce qui permet un accès direct aux fichiers et facilite le développement.  
    Les fichiers dépendent du système de fichiers de l'hôte, ce qui réduit la portabilité et peut poser des problèmes en production.
- **Les volumes managés** sont créés et gérés par Docker. Ils sont stockés dans un emplacement interne et peuvent être utilisés par plusieurs conteneurs. Ils offrent une meilleure isolation et une meilleure portabilité.  
    Leur usage est idéal pour la persistance des données (bases de données, application, etc.), mais ils nécessitent des commandes Docker pour y accéder.

<p class="callout info">L'exemple ci-dessus est un volume mappé.</p>

### Gestion des volumes managés

Les volumes managés docker sont gérés avec la commande `docker volume`.

```bash
docker volume create mon_volume # créer un volume
docker volume ls # lister les volumes
docker volume rm mon_volume # supprimer un volume
```

Lors de l'exécution d'un conteneur, il faut aussi utiliser `-v` pour monter un volume managé. À l'inverse d'un volume mappé, il faut indiquer le nom du volume plutôt que son chemin.

```bash
docker run -v mon_volume:/home/thibaud/volume ubuntu
# Le contenu du volume mon_volume sera monté dans /home/thibaud/volume au sein du conteneur
```

Le volume est indépendant du conteneur auquel il est attaché :

[![image.png](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/scaled-1680-/g8ehUdRUkjnCiHhw-image.png)](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/g8ehUdRUkjnCiHhw-image.png)

- Le volume `mon_volume` est attaché à un conteneur `ubuntu` dans `/home/toto` et le fichier `contenu.txt` y est créé.
- Lorsque le conteneur est arrêté, il est automatiquement supprimé puisqu'il a été lancé avec `--rm`.
- Le volume `mon_volume` persiste et son contenu est préservé.
- Un nouveau conteneur `debian` est lancé et le volume `mon_volume` y est attaché dans `/var/test` : le fichier `contenu.txt` est disponible.

Une subtilité des volumes managés est **la priorité des données** si l'un des deux répertoires est vide lors du lancement d'un conteneur :

<table border="1" id="bkmrk-contenu-du-volume-co" style="border-collapse: collapse; width: 100%; height: 199.766px;"><colgroup><col style="width: 17.2825%;"></col><col style="width: 31.7129%;"></col><col style="width: 51.0046%;"></col></colgroup><thead><tr style="height: 55.2188px;"><td style="height: 55.2188px;">**Volume**</td><td style="height: 55.2188px;">**Point de montage (conteneur)**</td><td style="height: 55.2188px;">**Action**</td></tr></thead><tbody><tr style="height: 76.3281px;"><td style="height: 76.3281px;">Si vide</td><td style="height: 76.3281px;">Si non vide</td><td style="height: 76.3281px;">Les données initialement dans le conteneur sont copiées dans le volume.</td></tr><tr style="height: 34.1094px;"><td style="height: 34.1094px;">Si non vide</td><td style="height: 34.1094px;">Si vide</td><td style="height: 34.1094px;">Le contenu du volume est monté dans le conteneur, comme dans l'exemple ci-dessus.</td></tr><tr style="height: 34.1094px;"><td style="height: 34.1094px;">Si non vide</td><td style="height: 34.1094px;">Si non vide</td><td style="height: 34.1094px;">Le contenu du répertoire dans le conteneur est écrasé par celui du volume.</td></tr></tbody></table>

# La gestion des ports

### Redirection des ports

La plupart des applications dockerisées écoutent sur des ports spécifiques. Par exemple : serveurs web, bases de données, API, etc.

Pour les rendre accessibles depuis l'extérieur, **il faut rediriger ces ports** avec l'option `--port`, `-p`.  
**Cette option permet de lier un port du conteneur à un port de la machine hôte.**

Dans le détail, lorsqu'une requête est envoyée à un port de la machine hôte, Docker la redirige automatiquement vers le port correspondant à l'intérieur du conteneur.

La syntaxe de `-p` est la suivante :

```bash
-p [IP:]PORT_HOTE:PORT_CONTENEUR
```

- **`PORT_CONTENEUR`** : le port sur lequel l'application à l'intérieur du conteneur écoute.
- **`PORT_HOTE`** : le port de la machine hôte sur lequel les requêtes externes seront reçues.
- **`IP` (facultative)** : permet de restreindre l'accès à une IP spécifique, par exemple `127.0.0.1`.

<p class="callout info">La redirection de ports docker permet de répondre à des **cas d'usages fréquents** : contrôle d'accès aux applications, gestion des conflits de port avec des applications similaires, configuration d'architectures réseaux complexes, etc.</p>

### Exemples avec strm/helloworld-http

L'image [ 🔗 strm/helloworld-http](https://hub.docker.com/r/strm/helloworld-http/) permet d'afficher un message test et écoute sur le port `80`, port `HTTP` par défaut.

Une fois lancé, un `docker ps -a` nous confirme que le port `80` du conteneur est ouvert.

[![image.png](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/scaled-1680-/31T6VfCRO3aHtsun-image.png)](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/31T6VfCRO3aHtsun-image.png)

[![image.png](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/scaled-1680-/J3oNu6UmNP7ZdI2C-image.png)](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/J3oNu6UmNP7ZdI2C-image.png)

Néanmoins, le port `80` de la machine hôte **ne répond pas** :

[![image.png](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/scaled-1680-/6MPwn428WptYXigg-image.png)](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/6MPwn428WptYXigg-image.png)

<p class="callout warning">Le port `80` de la machine hôte **n'est pas automatiquement redirigé** vers celui du conteneur.</p>

Il faut lancer le container avec l'argument `-p`. Il est tout à fait possible d'indiquer un autre port pour l'hôte, par exemple `90`.

[![image.png](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/scaled-1680-/f6NV3uszBUw9Otst-image.png)](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/f6NV3uszBUw9Otst-image.png)

Un `docker ps -a` nous confirme que le port `90` de la machine hôte est redirigé vers le port `80` du conteneur.

[![image.png](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/scaled-1680-/eDMbtQqFJsv7MybL-image.png)](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/eDMbtQqFJsv7MybL-image.png)

La page web est accessible depuis `127.0.0.1:90`. Elle est également accessible depuis le LAN, toujours sur le port `90`.

[![image.png](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/scaled-1680-/FtS0WTjyWoROAuw5-image.png)](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/FtS0WTjyWoROAuw5-image.png)

Il est possible de n'écouter que depuis l'IP `127.0.0.1` avec `-p 127.0.0.1:90:80`.

[![image.png](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/scaled-1680-/8Pqr2DolnI37IyU5-image.png)](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/8Pqr2DolnI37IyU5-image.png)

<p class="callout info">Cela peut-être utile pour faire fonctionner une application derrière un reverse proxy (traefik, apache, nginx, etc.) afin de lui attribuer un nom de domaine.</p>

# TP : Application PHP

### Objectif

L'objectif est de lancer un script PHP dans **deux conteneurs avec des versions PHP distinctes**.  
Il faudra au préalable lancer un conteneur temporaire afin de **télécharger le script PHP dans un volume managé**.

<p class="callout info">Aucune connaissance de PHP n'est requise.</p>

### Consignes

#### 1 - Récupération du script PHP dans un volume docker

- Créez un volume docker managé
- Lancez un `bash` dans un conteneur à partir de l'image `debian` et mappez le volume sur `/tp_docker`
- Exécutez les commandes suivantes afin de récupérer le fichier `index.php` depuis le conteneur

```bash
cd /tp_docker
apt update && apt-get install wget -y
wget https://formation-tfrichet-assets.s3.fr-par.scw.cloud/docker-tp-1/index.php 
chmod 777 ./
```

#### 2 - Exécution avec PHP 8.2

- Lancez un conteneur à partir de l'image `php:8.2-apache`.
- Redirigez le port `1082` de l'hôte vers le port `80` du conteneur.
- Mappez votre volume vers `/var/www/html`.
- Lancez le conteneur et ouvrez un navigateur web sur [http://127.0.0.1:1082](http://127.0.0.1:1082). <p class="callout warning">Remplacez 127.0.0.1 par l'IP de votre hôte docker si nécessaire.</p>
- Un formulaire doit s'afficher, renseignez votre nom et validez.
- Des informations telles que la date, un UUID généré et des informations systèmes s'affichent.

#### 3 - Exécution avec PHP 8.3

- Lancez un autre conteneur lié au volume managé.
- Utilisez cette fois-ci l'image `php:8.3-apache` et redirigez le port `1083` de l'hôte vers le port `80` du conteneur.
- Rendez-vous sur [http://127.0.0.1:1083](http://127.0.0.1:1083).
- Votre nom et l'UUID s'affichent, la version de PHP est désormais en 8.3.

### Résultat attendu

<p class="callout warning">Les 3 captures d'écran suivantes sont attendues pour valider le TP.</p>

[![image.png](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/scaled-1680-/Wdt8k60W8ZtdHO4o-image.png)](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/Wdt8k60W8ZtdHO4o-image.png)

[![image.png](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/scaled-1680-/mPIb6BYG3AXYpHgJ-image.png)](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/mPIb6BYG3AXYpHgJ-image.png)

[![image.png](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/scaled-1680-/uY8OwaARXwJeVm4Q-image.png)](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/uY8OwaARXwJeVm4Q-image.png)

# Chapitre 2 - Aller plus loin

# Le réseau

### Le réseau avec docker network

Docker permet une gestion avancée du réseau, entre un conteneur et la machine hôte, mais aussi entre les conteneurs.

Les réseaux docker peuvent être gérés avec la commande `docker network`.

```bash
docker network ls # lister les réseaux
docker network create mon_reseau # créer un réseau
docker network rm mon_reseau # supprimer un réseau
```

Il existe plusieurs types de réseaux docker, identifiés avec l'argument `--driver`.

- **Bridge (pont)**  
    Utilisé par défaut, c'est un réseau interne à docker.  
    Les conteneurs peuvent communiquer entre eux sur ce réseau.
- **Host**  
    Le conteneur partage directement le réseau de l'hôte. Il n'est pas isolé et utilise l'adresse IP de l'hôte. Dans ce cas, la redirection de port n'est pas nécessaire.
- **None**  
    Aucun réseau, isolation totale.

Docker propose par défaut un réseau pour chacun de ses 3 types.

[![image.png](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/scaled-1680-/4ZJOZgHHOHTvj3ig-image.png)](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/4ZJOZgHHOHTvj3ig-image.png)

Le réseau par défaut est `bridge`.

### Driver Bridge

Ci-dessous un exemple avec l'image `busybox` qui embarque quelques utilitaires réseau, dont la commande `ping`.

[![image.png](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/scaled-1680-/bnb61iRsLe3PAIf0-image.png)](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/bnb61iRsLe3PAIf0-image.png)

- Un réseau `testnetwork` est créé avec le driver `bridge`.
- Un conteneur `c1` est lancé en arrière-plan, il est attaché au réseau `testnetwork` avec `--network=testnetwork`.
- Un deuxième conteneur `c2` est lancé. Les deux conteneurs peuvent se pinguer.

<p class="callout info">Chaque conteneur est visible dans le réseau à partir de son nom défini avec `--name`.</p>

<p class="callout warning">Le réseau `bridge` par défaut de Docker isole les conteneurs entre-eux. Il est nécessaire de créer un autre réseau avec le driver `bridge` pour répéter l'expérience ci-dessus.</p>

Un conteneur situé en dehors du réseau `testnetwork` ne peut pas pinguer `c1` et `c2` :

[![image.png](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/scaled-1680-/603P8qQmJkNyr3EA-image.png)](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/603P8qQmJkNyr3EA-image.png)

<div drawio-diagram="87"><img src="https://formation.tfrichet.fr/uploads/images/drawio/2025-09/YFaBS9YrYcENeDbR-drawing-3-1758184551.png" alt=""/></div>

### Driver Host

Un conteneur lancé avec le driver `host` n'est pas isolé du réseau de l'hôte. Les ports ouverts sur le conteneur sont ouverts sur l'hôte.

Par exemple, un conteneur lancé à partir de l'image [ 🔗 strm/helloworld-http](https://hub.docker.com/r/strm/helloworld-http/) écoutera à partir du port `80` de l'hôte, même sans redirection avec `--port`.

[![image.png](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/scaled-1680-/qt82je0EU0HOofvt-image.png)](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/qt82je0EU0HOofvt-image.png)

[![image.png](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/scaled-1680-/uV5H00pUEj3FAYry-image.png)](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/uV5H00pUEj3FAYry-image.png)

<p class="callout info">Le nom d'hôte du conteneur est affiché. Il est hérité depuis la machine hôte.</p>

### Gestion des réseaux

Il est possible de connecter un conteneur à un réseau existant avec `docker network connect`.

[![image.png](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/scaled-1680-/3bocO0a2nHDec8fi-image.png)](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/3bocO0a2nHDec8fi-image.png)

Le détail d'un réseau peut être affiché avec la commande `docker network inspect`.

[![image.png](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/scaled-1680-/hhAvIiVSiofoxdGS-image.png)](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/hhAvIiVSiofoxdGS-image.png)

Un réseau `bridge` embarque une configuration IP, de la même manière qu'un réseau local.  
Le réseau est défini par un `subnet` et il possède une IP `gateway` attribuée à la machine hôte.

Dans l'exemple ci-dessus, le réseau `reseau2` possède le subnet `172.19.0.0` avec un masque de sous-réseau `255.255.0.0` (ou `/16`).  
L'adresse IP de la machine hôte est `172.19.0.1` et celle du conteneur `c5` est `172.19.0.2`.

# Administration : commandes utiles

<p class="callout info">Les commandes ci-dessous sont directement copiées depuis la documentation de docker :  
[ 🔗 https://docs.docker.com/reference/cli/docker/](https://docs.docker.com/reference/cli/docker/)</p>

Les usages principaux des commandes ont déjà été abordés jusqu'ici.

### docker image

<table id="bkmrk-command-description-" style="width: 99.7619%; height: 486.656px;"><thead><tr style="height: 34.1094px;"><th class="text-left" style="width: 25.5025%; height: 34.1094px;">**Command**</th><th class="text-left" style="width: 74.4975%; height: 34.1094px;">**Description**</th></tr></thead><tbody><tr style="height: 41.1406px;"><td class="text-left" style="width: 25.5025%; height: 41.1406px;">[`docker image history`](https://docs.docker.com/reference/cli/docker/image/history/)</td><td class="text-left" style="width: 74.4975%; height: 41.1406px;">Show the history of an image</td></tr><tr style="height: 41.1406px;"><td class="text-left" style="width: 25.5025%; height: 41.1406px;">[`docker image import`](https://docs.docker.com/reference/cli/docker/image/import/)</td><td class="text-left" style="width: 74.4975%; height: 41.1406px;">Import the contents from a tarball to create a filesystem image</td></tr><tr style="height: 41.1406px;"><td class="text-left" style="width: 25.5025%; height: 41.1406px;">[`docker image inspect`](https://docs.docker.com/reference/cli/docker/image/inspect/)</td><td class="text-left" style="width: 74.4975%; height: 41.1406px;">**Display detailed information on one or more images**</td></tr><tr style="height: 41.1406px;"><td class="text-left" style="width: 25.5025%; height: 41.1406px;">[`docker image load`](https://docs.docker.com/reference/cli/docker/image/load/)</td><td class="text-left" style="width: 74.4975%; height: 41.1406px;">Load an image from a tar archive or STDIN</td></tr><tr style="height: 41.1406px;"><td class="text-left" style="width: 25.5025%; height: 41.1406px;">[`docker image ls`](https://docs.docker.com/reference/cli/docker/image/ls/)</td><td class="text-left" style="width: 74.4975%; height: 41.1406px;">List images</td></tr><tr style="height: 41.1406px;"><td class="text-left" style="width: 25.5025%; height: 41.1406px;">[`docker image prune`](https://docs.docker.com/reference/cli/docker/image/prune/)</td><td class="text-left" style="width: 74.4975%; height: 41.1406px;">Remove unused images</td></tr><tr style="height: 41.1406px;"><td class="text-left" style="width: 25.5025%; height: 41.1406px;">[`docker image pull`](https://docs.docker.com/reference/cli/docker/image/pull/)</td><td class="text-left" style="width: 74.4975%; height: 41.1406px;">Download an image from a registry</td></tr><tr style="height: 41.1406px;"><td class="text-left" style="width: 25.5025%; height: 41.1406px;">[`docker image push`](https://docs.docker.com/reference/cli/docker/image/push/)</td><td class="text-left" style="width: 74.4975%; height: 41.1406px;">Upload an image to a registry</td></tr><tr style="height: 41.1406px;"><td class="text-left" style="width: 25.5025%; height: 41.1406px;">[`docker image rm`](https://docs.docker.com/reference/cli/docker/image/rm/)</td><td class="text-left" style="width: 74.4975%; height: 41.1406px;">Remove one or more images</td></tr><tr style="height: 41.1406px;"><td class="text-left" style="width: 25.5025%; height: 41.1406px;">[`docker image save`](https://docs.docker.com/reference/cli/docker/image/save/)</td><td class="text-left" style="width: 74.4975%; height: 41.1406px;">Save one or more images to a tar archive (streamed to STDOUT by default)</td></tr><tr style="height: 41.1406px;"><td class="text-left" style="width: 25.5025%; height: 41.1406px;">[`docker image tag`](https://docs.docker.com/reference/cli/docker/image/tag/)</td><td class="text-left" style="width: 74.4975%; height: 41.1406px;">Create a tag TARGET\_IMAGE that refers to SOURCE\_IMAGE</td></tr></tbody></table>

```json
debian@debian:~$ docker image inspect ubuntu
[
    {
        "Id": "sha256:e0f16e6366fef4e695b9f8788819849d265cde40eb84300c0147a6e5261d2750",
        "RepoTags": [
            "ubuntu:24.04",
            "ubuntu:latest"
        ],
        "RepoDigests": [
            "ubuntu@sha256:7c06e91f61fa88c08cc74f7e1b7c69ae24910d745357e0dfe1d2c0322aaf20f9"
        ],
        "Parent": "",
        "Comment": "",
        "Created": "2025-07-30T06:51:03.091147588Z",
        "DockerVersion": "24.0.7",
        "Author": "",
        "Architecture": "amd64",
        "Os": "linux",
        "Size": 78122494,
        "GraphDriver": {
            "Data": {
                "MergedDir": "/var/lib/docker/overlay2/8a59ab09262cee8d6004dcd5b978cd668baae2521d83a0621e5d1366fbd864a1/merged",
                "UpperDir": "/var/lib/docker/overlay2/8a59ab09262cee8d6004dcd5b978cd668baae2521d83a0621e5d1366fbd864a1/diff",
                "WorkDir": "/var/lib/docker/overlay2/8a59ab09262cee8d6004dcd5b978cd668baae2521d83a0621e5d1366fbd864a1/work"
            },
            "Name": "overlay2"
        },
        "RootFS": {
            "Type": "layers",
            "Layers": [
                "sha256:cd9664b1462ea111a41bdadf65ce077582cdc77e28683a4f6996dd03afcc56f5"
            ]
        },
        "Metadata": {
            "LastTagTime": "0001-01-01T00:00:00Z"
        },
        "Config": {
            "Cmd": [
                "/bin/bash"
            ],
            "Entrypoint": null,
            "Env": [
                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
            ],
            "Labels": {
                "org.opencontainers.image.ref.name": "ubuntu",
                "org.opencontainers.image.version": "24.04"
            },
            "OnBuild": null,
            "User": "",
            "Volumes": null,
            "WorkingDir": ""
        }
    }
]
```

### docker container

<table id="bkmrk-command-description--1"><thead><tr><th class="text-left">**Command**</th><th class="text-left">**Description**</th></tr></thead><tbody><tr><td class="text-left">[`docker container attach`](https://docs.docker.com/reference/cli/docker/container/attach/)</td><td class="text-left">Attach local standard input, output, and error streams to a running container</td></tr><tr><td class="text-left">[`docker container commit`](https://docs.docker.com/reference/cli/docker/container/commit/)</td><td class="text-left">Create a new image from a container's changes</td></tr><tr><td class="text-left">[`docker container cp`](https://docs.docker.com/reference/cli/docker/container/cp/)</td><td class="text-left">Copy files/folders between a container and the local filesystem</td></tr><tr><td class="text-left">[`docker container create`](https://docs.docker.com/reference/cli/docker/container/create/)</td><td class="text-left">Create a new container</td></tr><tr><td class="text-left">[`docker container diff`](https://docs.docker.com/reference/cli/docker/container/diff/)</td><td class="text-left">Inspect changes to files or directories on a container's filesystem</td></tr><tr><td class="text-left">[`docker container exec`](https://docs.docker.com/reference/cli/docker/container/exec/)</td><td class="text-left">Execute a command in a running container</td></tr><tr><td class="text-left">[`docker container export`](https://docs.docker.com/reference/cli/docker/container/export/)</td><td class="text-left">Export a container's filesystem as a tar archive</td></tr><tr><td class="text-left">[`docker container inspect`](https://docs.docker.com/reference/cli/docker/container/inspect/)</td><td class="text-left">Display detailed information on one or more containers</td></tr><tr><td class="text-left">[`docker container kill`](https://docs.docker.com/reference/cli/docker/container/kill/)</td><td class="text-left">Kill one or more running containers</td></tr><tr><td class="text-left">[`docker container logs`](https://docs.docker.com/reference/cli/docker/container/logs/)</td><td class="text-left">Fetch the logs of a container</td></tr><tr><td class="text-left">[`docker container ls`](https://docs.docker.com/reference/cli/docker/container/ls/)</td><td class="text-left">List containers</td></tr><tr><td class="text-left">[`docker container pause`](https://docs.docker.com/reference/cli/docker/container/pause/)</td><td class="text-left">Pause all processes within one or more containers</td></tr><tr><td class="text-left">[`docker container port`](https://docs.docker.com/reference/cli/docker/container/port/)</td><td class="text-left">List port mappings or a specific mapping for the container</td></tr><tr><td class="text-left">[`docker container prune`](https://docs.docker.com/reference/cli/docker/container/prune/)</td><td class="text-left">Remove all stopped containers</td></tr><tr><td class="text-left">[`docker container rename`](https://docs.docker.com/reference/cli/docker/container/rename/)</td><td class="text-left">Rename a container</td></tr><tr><td class="text-left">[`docker container restart`](https://docs.docker.com/reference/cli/docker/container/restart/)</td><td class="text-left">Restart one or more containers</td></tr><tr><td class="text-left">[`docker container rm`](https://docs.docker.com/reference/cli/docker/container/rm/)</td><td class="text-left">Remove one or more containers</td></tr><tr><td class="text-left">[`docker container run`](https://docs.docker.com/reference/cli/docker/container/run/)</td><td class="text-left">Create and run a new container from an image</td></tr><tr><td class="text-left">[`docker container start`](https://docs.docker.com/reference/cli/docker/container/start/)</td><td class="text-left">Start one or more stopped containers</td></tr><tr><td class="text-left">[`docker container stats`](https://docs.docker.com/reference/cli/docker/container/stats/)</td><td class="text-left">Display a live stream of container(s) resource usage statistics</td></tr><tr><td class="text-left">[`docker container stop`](https://docs.docker.com/reference/cli/docker/container/stop/)</td><td class="text-left">Stop one or more running containers</td></tr><tr><td class="text-left">[`docker container top`](https://docs.docker.com/reference/cli/docker/container/top/)</td><td class="text-left">Display the running processes of a container</td></tr><tr><td class="text-left">[`docker container unpause`](https://docs.docker.com/reference/cli/docker/container/unpause/)</td><td class="text-left">Unpause all processes within one or more containers</td></tr><tr><td class="text-left">[`docker container update`](https://docs.docker.com/reference/cli/docker/container/update/)</td><td class="text-left">Update configuration of one or more containers</td></tr><tr><td class="text-left">[`docker container wait`](https://docs.docker.com/reference/cli/docker/container/wait/)</td><td class="text-left">Block until one or more containers stop, then print their exit codes</td></tr></tbody></table>

```json
debian@debian:~$ docker inspect c5
[
    {
        "Id": "f5f93155c9e02fc6a56d40b6d1bf5d0f3cd5c73106c72882f8878038398fe158",
        "Created": "2025-08-29T20:35:19.745912071Z",
        "Path": "/bin/bash",
        "Args": [],
        "State": {
            "Status": "running",
            "Running": true,
            "Paused": false,
            "Restarting": false,
            "OOMKilled": false,
            "Dead": false,
            "Pid": 3405,
            "ExitCode": 0,
            "Error": "",
            "StartedAt": "2025-08-29T20:35:19.948819333Z",
            "FinishedAt": "0001-01-01T00:00:00Z"
        },
        "Image": "sha256:e0f16e6366fef4e695b9f8788819849d265cde40eb84300c0147a6e5261d2750",
        "ResolvConfPath": "/var/lib/docker/containers/f5f93155c9e02fc6a56d40b6d1bf5d0f3cd5c73106c72882f8878038398fe158/resolv.conf",
        "HostnamePath": "/var/lib/docker/containers/f5f93155c9e02fc6a56d40b6d1bf5d0f3cd5c73106c72882f8878038398fe158/hostname",
        "HostsPath": "/var/lib/docker/containers/f5f93155c9e02fc6a56d40b6d1bf5d0f3cd5c73106c72882f8878038398fe158/hosts",
        "LogPath": "/var/lib/docker/containers/f5f93155c9e02fc6a56d40b6d1bf5d0f3cd5c73106c72882f8878038398fe158/f5f93155c9e02fc6a56d40b6d1bf5d0f3cd5c73106c72882f8878038398fe158-json.log",
        "Name": "/c5",
        "RestartCount": 0,
        "Driver": "overlay2",
        "Platform": "linux",
        "MountLabel": "",
        "ProcessLabel": "",
        "AppArmorProfile": "docker-default",
        "ExecIDs": null,
        "HostConfig": {
            "Binds": null,
            "ContainerIDFile": "",
            "LogConfig": {
                "Type": "json-file",
                "Config": {}
            },
            "NetworkMode": "bridge",
            "PortBindings": {},
            "RestartPolicy": {
                "Name": "no",
                "MaximumRetryCount": 0
            },
            "AutoRemove": false,
            "VolumeDriver": "",
            "VolumesFrom": null,
            "ConsoleSize": [
                54,
                235
            ],
            "CapAdd": null,
            "CapDrop": null,
            "CgroupnsMode": "private",
            "Dns": [],
            "DnsOptions": [],
            "DnsSearch": [],
            "ExtraHosts": null,
            "GroupAdd": null,
            "IpcMode": "private",
            "Cgroup": "",
            "Links": null,
            "OomScoreAdj": 0,
            "PidMode": "",
            "Privileged": false,
            "PublishAllPorts": false,
            "ReadonlyRootfs": false,
            "SecurityOpt": null,
            "UTSMode": "",
            "UsernsMode": "",
            "ShmSize": 67108864,
            "Runtime": "runc",
            "Isolation": "",
            "CpuShares": 0,
            "Memory": 0,
            "NanoCpus": 0,
            "CgroupParent": "",
            "BlkioWeight": 0,
            "BlkioWeightDevice": [],
            "BlkioDeviceReadBps": [],
            "BlkioDeviceWriteBps": [],
            "BlkioDeviceReadIOps": [],
            "BlkioDeviceWriteIOps": [],
            "CpuPeriod": 0,
            "CpuQuota": 0,
            "CpuRealtimePeriod": 0,
            "CpuRealtimeRuntime": 0,
            "CpusetCpus": "",
            "CpusetMems": "",
            "Devices": [],
            "DeviceCgroupRules": null,
            "DeviceRequests": null,
            "MemoryReservation": 0,
            "MemorySwap": 0,
            "MemorySwappiness": null,
            "OomKillDisable": null,
            "PidsLimit": null,
            "Ulimits": [],
            "CpuCount": 0,
            "CpuPercent": 0,
            "IOMaximumIOps": 0,
            "IOMaximumBandwidth": 0,
            "MaskedPaths": [
                "/proc/asound",
                "/proc/acpi",
                "/proc/interrupts",
                "/proc/kcore",
                "/proc/keys",
                "/proc/latency_stats",
                "/proc/timer_list",
                "/proc/timer_stats",
                "/proc/sched_debug",
                "/proc/scsi",
                "/sys/firmware",
                "/sys/devices/virtual/powercap"
            ],
            "ReadonlyPaths": [
                "/proc/bus",
                "/proc/fs",
                "/proc/irq",
                "/proc/sys",
                "/proc/sysrq-trigger"
            ]
        },
        "GraphDriver": {
            "Data": {
                "ID": "f5f93155c9e02fc6a56d40b6d1bf5d0f3cd5c73106c72882f8878038398fe158",
                "LowerDir": "/var/lib/docker/overlay2/9c831cc72a1726631cb4c9e776e29e8192a4793b9172c2fabf2226fdfb8babb9-init/diff:/var/lib/docker/overlay2/8a59ab09262cee8d6004dcd5b978cd668baae2521d83a0621e5d1366fbd864a1/diff",
                "MergedDir": "/var/lib/docker/overlay2/9c831cc72a1726631cb4c9e776e29e8192a4793b9172c2fabf2226fdfb8babb9/merged",
                "UpperDir": "/var/lib/docker/overlay2/9c831cc72a1726631cb4c9e776e29e8192a4793b9172c2fabf2226fdfb8babb9/diff",
                "WorkDir": "/var/lib/docker/overlay2/9c831cc72a1726631cb4c9e776e29e8192a4793b9172c2fabf2226fdfb8babb9/work"
            },
            "Name": "overlay2"
        },
        "Mounts": [],
        "Config": {
            "Hostname": "f5f93155c9e0",
            "Domainname": "",
            "User": "",
            "AttachStdin": false,
            "AttachStdout": false,
            "AttachStderr": false,
            "Tty": true,
            "OpenStdin": true,
            "StdinOnce": false,
            "Env": [
                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
            ],
            "Cmd": [
                "/bin/bash"
            ],
            "Image": "ubuntu",
            "Volumes": null,
            "WorkingDir": "",
            "Entrypoint": null,
            "OnBuild": null,
            "Labels": {
                "org.opencontainers.image.ref.name": "ubuntu",
                "org.opencontainers.image.version": "24.04"
            }
        },
        "NetworkSettings": {
            "Bridge": "",
            "SandboxID": "34132282ba3e544d46aec112f27ed949a571b114ebc2fee664fc034776e06be7",
            "SandboxKey": "/var/run/docker/netns/34132282ba3e",
            "Ports": {},
            "HairpinMode": false,
            "LinkLocalIPv6Address": "",
            "LinkLocalIPv6PrefixLen": 0,
            "SecondaryIPAddresses": null,
            "SecondaryIPv6Addresses": null,
            "EndpointID": "c239ab855b930537d28d6ebe5343f7eb9241eca8c7e086fe9894aa19c5f7cce7",
            "Gateway": "172.17.0.1",
            "GlobalIPv6Address": "",
            "GlobalIPv6PrefixLen": 0,
            "IPAddress": "172.17.0.2",
            "IPPrefixLen": 16,
            "IPv6Gateway": "",
            "MacAddress": "56:4e:03:5c:c5:d4",
            "Networks": {
                "bridge": {
                    "IPAMConfig": null,
                    "Links": null,
                    "Aliases": null,
                    "MacAddress": "56:4e:03:5c:c5:d4",
                    "DriverOpts": null,
                    "GwPriority": 0,
                    "NetworkID": "e5ee2347d56211a8f10602c4ebc07b27a15b2f9507af47f3c7c16bdbcf18fc6c",
                    "EndpointID": "c239ab855b930537d28d6ebe5343f7eb9241eca8c7e086fe9894aa19c5f7cce7",
                    "Gateway": "172.17.0.1",
                    "IPAddress": "172.17.0.2",
                    "IPPrefixLen": 16,
                    "IPv6Gateway": "",
                    "GlobalIPv6Address": "",
                    "GlobalIPv6PrefixLen": 0,
                    "DNSNames": null
                },
                "reseau2": {
                    "IPAMConfig": {},
                    "Links": null,
                    "Aliases": [],
                    "MacAddress": "5a:76:dd:aa:62:15",
                    "DriverOpts": {},
                    "GwPriority": 0,
                    "NetworkID": "562ad4a6e8b3105f619bd0deb30b89d5aaddde71babd6422ad912ccfa327687d",
                    "EndpointID": "f71251f1d3c4d9be09376f687898d22a984017b91f6da6c8fe75fa65dedba007",
                    "Gateway": "172.19.0.1",
                    "IPAddress": "172.19.0.2",
                    "IPPrefixLen": 16,
                    "IPv6Gateway": "",
                    "GlobalIPv6Address": "",
                    "GlobalIPv6PrefixLen": 0,
                    "DNSNames": [
                        "c5",
                        "f5f93155c9e0"
                    ]
                }
            }
        }
    }
]
```

### docker volume

<table id="bkmrk-command-description--2"><thead><tr><th class="text-left">**Command**</th><th class="text-left">**Description**</th></tr></thead><tbody><tr><td class="text-left">[`docker volume create`](https://docs.docker.com/reference/cli/docker/volume/create/)</td><td class="text-left">Create a volume</td></tr><tr><td class="text-left">[`docker volume inspect`](https://docs.docker.com/reference/cli/docker/volume/inspect/)</td><td class="text-left">**Display detailed information on one or more volumes**</td></tr><tr><td class="text-left">[`docker volume ls`](https://docs.docker.com/reference/cli/docker/volume/ls/)</td><td class="text-left">List volumes</td></tr><tr><td class="text-left">[`docker volume prune`](https://docs.docker.com/reference/cli/docker/volume/prune/)</td><td class="text-left">Remove unused local volumes</td></tr><tr><td class="text-left">[`docker volume rm`](https://docs.docker.com/reference/cli/docker/volume/rm/)</td><td class="text-left">Remove one or more volumes</td></tr><tr><td class="text-left">[`docker volume update`](https://docs.docker.com/reference/cli/docker/volume/update/)</td><td class="text-left">Update a volume (cluster volumes only)</td></tr></tbody></table>

[![image.png](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/scaled-1680-/KcPPlrZMENIKIevl-image.png)](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/KcPPlrZMENIKIevl-image.png)

### docker network

<table id="bkmrk-command-description--3"><thead><tr><th class="text-left">**Command**</th><th class="text-left">**Description**</th></tr></thead><tbody><tr><td class="text-left">[`docker network connect`](https://docs.docker.com/reference/cli/docker/network/connect/)</td><td class="text-left">Connect a container to a network</td></tr><tr><td class="text-left">[`docker network create`](https://docs.docker.com/reference/cli/docker/network/create/)</td><td class="text-left">Create a network</td></tr><tr><td class="text-left">[`docker network disconnect`](https://docs.docker.com/reference/cli/docker/network/disconnect/)</td><td class="text-left">Disconnect a container from a network</td></tr><tr><td class="text-left">[`docker network inspect`](https://docs.docker.com/reference/cli/docker/network/inspect/)</td><td class="text-left">Display detailed information on one or more networks</td></tr><tr><td class="text-left">[`docker network ls`](https://docs.docker.com/reference/cli/docker/network/ls/)</td><td class="text-left">List networks</td></tr><tr><td class="text-left">[`docker network prune`](https://docs.docker.com/reference/cli/docker/network/prune/)</td><td class="text-left">Remove all unused networks</td></tr><tr><td class="text-left">[`docker network rm`](https://docs.docker.com/reference/cli/docker/network/rm/)</td><td class="text-left">Remove one or more networks</td></tr></tbody></table>

[![image.png](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/scaled-1680-/m5C1fUPv4cQk9Rf8-image.png)](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/m5C1fUPv4cQk9Rf8-image.png)

### docker stats

La commande `docker stats` affiche les informations essentielles des conteneurs actuellement lancés. Par défaut, la commande s'actualise toutes les 2 secondes.

[![image.png](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/scaled-1680-/88bPpB5h2yRXIkV8-image.png)](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/88bPpB5h2yRXIkV8-image.png)

### docker system

<table id="bkmrk-command-description--4"><thead><tr><th class="text-left">**Command**</th><th class="text-left">**Description**</th></tr></thead><tbody><tr><td class="text-left">[`docker system df`](https://docs.docker.com/reference/cli/docker/system/df/)</td><td class="text-left">Show docker disk usage</td></tr><tr><td class="text-left">[`docker system events`](https://docs.docker.com/reference/cli/docker/system/events/)</td><td class="text-left">Get real time events from the server</td></tr><tr><td class="text-left">[`docker system info`](https://docs.docker.com/reference/cli/docker/system/info/)</td><td class="text-left">Display system-wide information</td></tr><tr><td class="text-left">[`docker system prune`](https://docs.docker.com/reference/cli/docker/system/prune/)</td><td class="text-left">Remove unused data</td></tr></tbody></table>

[![image.png](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/scaled-1680-/q5rCb7LoqsWxSIWd-image.png)](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/q5rCb7LoqsWxSIWd-image.png)

# Administration avec Portainer

### Interface graphique Portainer

Portainer est une interface web qui permet de gérer facilement des environnements Docker. Elle simplifie l'administration des conteneurs, images, volumes et réseaux sans avoir à utiliser la ligne de commande.

[![image.png](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/scaled-1680-/0HqJnnWRaIeciMq4-image.png)](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/0HqJnnWRaIeciMq4-image.png)

Portainer est une solution commerciale basée sur un outil open-source.Ci-dessous un extrait du site [ 🔗 docs.portainer.io](https://docs.portainer.io/) :

> **Portainer Community Edition (CE)** is our foundation. With over half a million regular users, CE is a powerful, open source toolset that allows you to easily build and manage containers in Docker, Docker Swarm, Kubernetes and Azure ACI.
> 
> **Portainer Business Edition (BE)** is our commercial offering. With features geared towards businesses and larger organizations such as [Role-Based Access Control](https://docs.portainer.io/admin/user/roles), [registry management](https://docs.portainer.io/admin/registries/browse), and [dedicated support](https://docs.portainer.io/#getting-support), Portainer BE is a powerful toolset that allows you to easily build and manage containers in Docker, Docker Swarm, Kubernetes, Podman and Azure ACI.

Nous utiliserons **Portainer Community Edition (CE)**.

### Installation

<p class="callout info">La procédure d'installation détaillée est disponible sur [ 🔗 docs.portainer.io](https://docs.portainer.io/start/install-ce/server/docker/linux).</p>

L'installation de Portainer s'effectue simplement avec une image docker :

```bash
docker volume create portainer_data
docker run -d -p 8000:8000 -p 9443:9443 --name portainer --restart=always \
-v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer-ce:lts

```

Portainer est accessible à l'adresse : [ 🔗 ](https://127.0.0.1:9443/)[https://127.0.0.1:9443/](https://127.0.0.1:9443/).

<p class="callout warning">Remplacez 127.0.0.1 par l'IP de votre hôte docker si nécessaire.</p>

La création d'un utilisateur est obligatoire avant la première connexion.

[![image.png](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/scaled-1680-/n9LmUAM0yloJKFBp-image.png)](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/n9LmUAM0yloJKFBp-image.png)

Il faut ensuite sélectionner l'environnement local.

[![image.png](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/scaled-1680-/MmBghWudeqj5RZJQ-image.png)](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/MmBghWudeqj5RZJQ-image.png)

L'administration des containers, images, volumes, network est disponible.

[![image.png](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/scaled-1680-/EIuhypLARw2EWfwL-image.png)](https://formation.tfrichet.fr/uploads/images/gallery/2025-08/EIuhypLARw2EWfwL-image.png)

# Construction d'image avec Dockerfile

### Dockerfile et layers

Les images Docker fonctionnent par couches successives d'instructions, **les layers**.

Un **Dockerfile** est un fichier texte qui décrit les différentes couches d'une image Docker.

Chaque couche permet d'ajouter des actions à l'image, par exemple :

- Installation de dépendances
- Configuration d'un environnement
- Copie de fichiers depuis la machine hôte
- Définition de la commande lancée au démarrage du conteneur
- Etc.

<p class="callout info">Le dockerfile est comparable à une recette de cuisine : chaque instruction du Dockerfile représente une étape dans la préparation de l’environnement logiciel souhaité.</p>

Son utilisation offre certains avantages :

- **Automatisation** : chaque build est reproductible et scripté.
- **Traçabilité** : le fichier peut être versionné avec Git.
- **Portabilité** : un Dockerfile peut être utilisé sur n'importe quelle machine.
- **Modularité** : il permet de construire des images à partir d’autres images, facilitant la réutilisation.

Ci-dessous un exemple de Dockerfile :

```bash
FROM debian:trixie # point de départ : une image existante
RUN apt-get update -y # commande à éxécuter
RUN apt-get install fastfetch -y # une autre commande, pour une nouvelle couche
ENTRYPOINT ["fastfetch"] # point d'entrée au lancement du conteneur
```

### Les instructions d'un Dockerfile

<p class="callout info">N'hésitez pas à consulter la documentation de Docker à ce sujet : [🔗 Dockerfile reference](https://docs.docker.com/reference/dockerfile/).</p>

#### `FROM` - Définir l'image de base

```bash
FROM ubuntu:24.04
```

C'est le point de départ de l'image : une autre image de base officielle ou personnalisée.

#### `LABEL` - Ajouter des métadonnées

```bash
LABEL Maintainer="Thibaud FRICHET"
LABEL Description="Description de l'image"
```

Les métadonnées de l'image s'affichent avec `docker image inspect`.

#### `WORKDIR` - Définir le répertoire de travail

```bash
WORKDIR /var/www/html
```

Les instructions `RUN`, `CMD` et `ENTRYPOINT` s’exécutent dans le répertoire de travail.

#### `COPY` - Copier des fichiers locaux dans l'image

```bash
# copie fichier.txt à partir du répertoire courant de la machine hôte
# dans le répertoire courant de l'image (défini avec WORKDIR)
COPY fichier.txt ./ 

# copie le répertoire sources depuis la machine hôte vers le répertoire /var/www de l'image
COPY /home/thibaud/projet/sources/ /var/www/
```

#### `RUN` - Exécuter des commandes pendant la construction

```bash
# installation et lancement de Redis
RUN apt-get update -y
RUN apt-get install redis-server -y
RUN service redis-server start
```

Les commandes `RUN` s’exécutent pendant la construction de l'image, pas au démarrage d'un conteneur.

<p class="callout warning">Les commandes doivent se terminer sans saisie utilisateur.</p>

##### `EXPOSE` - Indiquer les ports utilisés par l'application

```bash
EXPOSE 80
EXPOSE 443
```

Ce sont les ports d'écoute du conteneur, ils peuvent être redirigés avec `docker run -p 90:80` à l'exécution.

##### `CMD` - Définir les commandes par défaut à exécuter

L'instruction CMD accepte un tableau en paramètre.

```bash
CMD ["date", "echo Bonjour"] # affiche la date courante, puis "Bonjour"
```

Contrairement à `RUN`, l'instruction `CMD` est exécutée au démarrage du conteneur, pas pendant la construction.

##### `ENTRYPOINT` - Définir le point d'entrée du conteneur

```bash
ENTRYPOINT ["service nginx start"] # lance le serveur web nginx
```

`CMD` et `ENTRYPOINT` définissent les commandes exécutées au démarrage d'un conteneur, mais avec un comportement différent :

- `CMD` indique la commande avec des arguments par défaut qui peuvent être remplacés lors de l’exécution avec docker run.
- `ENTRYPOINT` fixe le point d’entrée du conteneur, rendant son comportement plus rigide.

Lorsqu’ils sont utilisés ensemble, `ENTRYPOINT` définit le programme à exécuter et `CMD` les arguments par défaut.

<p class="callout info">Dans la majorité des cas, vous pouvez utiliser l'un ou l'autre uniquement.</p>

### Bonnes pratiques

- Utilisez des images légères. Exemples : `python:3.10-slim`, `nginx-stable-alpine`, etc.
- Utilisez des versions fixes dans vos dépendances. Évitez les tags `-latest` par exemple.
- Minimisez le nombre de couches, par exemple avec plusieurs commandes sur une ligne `RUN` et `&&`.  
    ```
    RUN apt-get update -y && apt-get install redis-server
    ```
- Utilisez `.dockerignore` pour exclure les fichiers inutiles, de la même manière qu'un `.gitignore`.  
    Le contenu du répertoire courant de la machine hôte est envoyé à Docker lors de la construction d'une image. Docker appelle cela le `context`.
- Respectez la convention de nommage d'un dockerfile : 
    - Par défaut : `Dockerfile`
    - Avec un préfixe : `dev.Dockerfile`, `prod.Dockerfile`, `app-v3.Dockerfile`

### La construction de l'image

La construction d'une image docker s'effectue avec `docker build`.

Le paramètre `-t`, `--tag` indique le nom de l'image et sa version. Le paramètre `-f`, `--file` le nom du fichier dockerfile, par défaut `Dockerfile`.

Un répertoire de contexte doit être indiqué, par exemple le répertoire courant avec `.`.

```bash
docker build -t mon_appli:1.0.0 -f monappli.Dockerfile .
```

[![image.png](https://formation.tfrichet.fr/uploads/images/gallery/2025-09/scaled-1680-/tZrv72npXnLC5DX7-image.png)](https://formation.tfrichet.fr/uploads/images/gallery/2025-09/tZrv72npXnLC5DX7-image.png)

[![image.png](https://formation.tfrichet.fr/uploads/images/gallery/2025-09/scaled-1680-/AJw5mu7IRM7XQskp-image.png)](https://formation.tfrichet.fr/uploads/images/gallery/2025-09/AJw5mu7IRM7XQskp-image.png)

<p class="callout info">Par la suite, une image Docker peut être exportée dans un fichier ou uploadée sur un registre privé par exemple.</p>

# TP : Création d'une image Docker

### Objectif

L'objectif est de **construire une image** qui embarque un script Python accessible en web.

<p class="callout info">Aucune connaissance de Python n'est requise.</p>

### Consignes

#### 1 - Téléchargement de l'application

Téléchargez le fichier zip suivant et dézippez-le.

**[🔗 https://formation-tfrichet-assets.s3.fr-par.scw.cloud/docker-tp-2/docker-tp-2.zip](https://formation-tfrichet-assets.s3.fr-par.scw.cloud/docker-tp-2/docker-tp-2.zip)**

Vous obtenez un dossier `app`.

[![image.png](https://formation.tfrichet.fr/uploads/images/gallery/2025-09/scaled-1680-/OifzVjWzhc98uEVw-image.png)](https://formation.tfrichet.fr/uploads/images/gallery/2025-09/OifzVjWzhc98uEVw-image.png)

#### 2 - Création du Dockerfile

**Créez et buildez** un Dockerfile en respectant les consignes ci-dessous.

- L'image de départ est `python:3.10-slim`.
- Le répertoire de travail est `/app`.
- Pendant la construction de l'image, les commandes doivent être exécutées :  
    ```bash
    apt-get update && apt-get install -y procps
    pip install flask
    ```
- L'application (dossier `app` téléchargé) doit être copiée dans le répertoire de travail de l'image.
- L'image écoute sur le port `5000`.
- Les commandes suivantes sont exécutées au démarrage d'un conteneur : `python` et `app.py`.
- Nommez l'image `tp2`.

#### 3 - Exécution d'un conteneur

Exécutez un conteneur avec la commande ci-dessous. N'oubliez pas de renseigner votre nom.

```bash
docker run -ti --rm -p 5000:5000 -e NAME=VOTRE_NOM_ICI tp2
```

Rendez-vous sur [http://127.0.0.1:5000](http://127.0.0.1:5000).

### Résultat attendu

<p class="callout warning">Le contenu du Dockerfile et la capture d'écran ci-dessous sont attendus pour valider le TP.</p>

[![image.png](https://formation.tfrichet.fr/uploads/images/gallery/2025-09/scaled-1680-/OAQ8Oy4wG8RUSpSK-image.png)](https://formation.tfrichet.fr/uploads/images/gallery/2025-09/OAQ8Oy4wG8RUSpSK-image.png)

# Chapitre 3 - La stack

# Docker Compose

### Introduction

**Docker Compose** est une commande docker permettant de définir et de gérer des applications multi-conteneurs.

La commande utilise un fichier YAML (`docker-compose.yml)` pour décrire les services, les réseaux, les volumes, et les dépendances entre les conteneurs.

Ci-dessous l'exemple d'un fichier `docker-compose.yml`. Il s'agit d'un projet web avec une API Node.js, une base de données PostgreSQL, et un reverse proxy Nginx.

```yaml
services:
  nginx:
    image: nginx
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
    depends_on:
      - web

  web:
    build: ./web
    environment:
      - NODE_ENV=production
    depends_on:
      - db

  db:
    image: postgres
    environment:
      - POSTGRES_PASSWORD=secret
    volumes:
      - pgdata:/var/lib/postgresql/data

volumes:
  pgdata:

```

La "traduction" en ligne de commandes `docker` ressemblerait à ceci :

```bash
# création du volume pour PostgreSQL
docker volume create pgdata

# création de l'image pour le serveur web
docker build -t web-image ./web

# lancement des conteneurs
docker run -d --name db -e POSTGRES_PASSWORD=secret -v pgdata:/var/lib/postgresql/data postgres
docker run -d --name web -e NODE_ENV=production web-image
docker run -d --name nginx --network frontnet -p 80:80 -v ./nginx.conf:/etc/nginx/nginx.conf nginx

```

### Orchestration

L'orchestration d'une stack docker compose s'effectue avec la commande `docker compose`.

```bash
docker compose up # démarre les conteneurs
docker compose down # arrête et supprime les conteneurs, réseaux et volumes
docker compose build # construit les images des conteneurs
docker compose logs # affiche les logs des conteneurs
```

### Syntaxe

#### Services

La clé `services` définit les conteneurs à lancer. Chaque service représente un conteneur Docker.

```yaml
services:
  web:
    image: nginx:latest
```

Pour chaque service, les instructions suivantes sont disponibles.

<table border="1" id="bkmrk-cl%C3%A9-description-exem" style="width: 100%; border-collapse: collapse; height: 586.578px;"><thead><tr style="height: 34.1094px;"><th style="width: 18.9511%; height: 34.1094px;">**Instruction**</th><th style="width: 44.4627%; height: 34.1094px;">**Description**</th><th style="width: 36.5862%; height: 34.1094px;">**Exemple**</th></tr></thead><tbody><tr style="height: 34.1094px;"><td style="width: 18.9511%; height: 34.1094px;">`image`</td><td style="width: 44.4627%; height: 34.1094px;">Image de départ</td><td style="width: 36.5862%; height: 34.1094px;">`image: ubuntu:24:04`</td></tr><tr style="height: 34.2188px;"><td style="width: 18.9511%; height: 34.2188px;">`container_name`</td><td style="width: 44.4627%; height: 34.2188px;">Nom du conteneur</td><td style="width: 36.5862%; height: 34.2188px;">`container_name: mon_web`</td></tr><tr style="height: 85.375px;"><td style="width: 18.9511%; height: 85.375px;">`ports`</td><td style="width: 44.4627%; height: 85.375px;">Mappe les ports de la machine hôte vers le conteneur</td><td style="width: 36.5862%; height: 85.375px;">```yaml
ports:
  - 8080:80
```

</td></tr><tr style="height: 85.375px;"><td style="width: 18.9511%; height: 85.375px;">`volumes`</td><td style="width: 44.4627%; height: 85.375px;">Monte des volumes dans le conteneur. Volume mappés ou managés.</td><td style="width: 36.5862%; height: 85.375px;">```yaml
volumes:
  - ./mon_dossier:/app
```

</td></tr><tr style="height: 85.375px;"><td style="width: 18.9511%; height: 85.375px;">`environment`</td><td style="width: 44.4627%; height: 85.375px;">Variables d'environnement</td><td style="width: 36.5862%; height: 85.375px;">```yaml
environment:
  - DEBUG=1
```

</td></tr><tr style="height: 55.2188px;"><td style="width: 18.9511%; height: 55.2188px;">`command`</td><td style="width: 44.4627%; height: 55.2188px;">Commande au démarrage du conteneur</td><td style="width: 36.5862%; height: 55.2188px;">`command: ["python", "app.py"]`</td></tr><tr style="height: 34.1094px;"><td style="width: 18.9511%; height: 34.1094px;">`depends_on`</td><td style="width: 44.4627%; height: 34.1094px;">Ordre de création des conteneurs</td><td style="width: 36.5862%; height: 34.1094px;">`depends_on: ["db"]`</td></tr><tr style="height: 76.3125px;"><td style="width: 18.9511%; height: 76.3125px;">`restart`</td><td style="width: 44.4627%; height: 76.3125px;">Politique de redémarrage (`no`, `always`, `on-failure`, `unless-stopped`)</td><td style="width: 36.5862%; height: 76.3125px;">`restart: always`</td></tr><tr style="height: 62.375px;"><td style="width: 18.9511%; height: 62.375px;">`networks`</td><td style="width: 44.4627%; height: 62.375px;">Réseaux du conteneur</td><td style="width: 36.5862%; height: 62.375px;">```yaml
networks:
  - frontend
```

</td></tr></tbody></table>

#### Volumes

La clé `volumes` permet d'automatiser la création et le montage des volumes managés.

```yaml
volumes:
  db_data # volume sans paramètres spécifiques
  external_volume:
    external: true
    name: mon_volume_externe
```

<table border="1" id="bkmrk-instruction-descript" style="width: 100%; border-collapse: collapse; height: 123.438px;"><thead><tr style="height: 34.1094px;"><th style="width: 18.9511%; height: 34.1094px;">**Instruction**</th><th style="width: 44.4627%; height: 34.1094px;">**Description**</th><th style="width: 36.5862%; height: 34.1094px;">**Exemple**</th></tr></thead><tbody><tr style="height: 55.2188px;"><td style="width: 18.9511%; height: 55.2188px;">`external`</td><td style="width: 44.4627%; height: 55.2188px;">Indique si le volume a été créé en dehors de la stack docker compose.</td><td style="width: 36.5862%; height: 55.2188px;">`external: true`</td></tr><tr style="height: 34.1094px;"><td style="width: 18.9511%; height: 34.1094px;">`name`</td><td style="width: 44.4627%; height: 34.1094px;">Nom du volume, utile avec `external`</td><td style="width: 36.5862%; height: 34.1094px;">`name: mon_volume_externe`

</td></tr></tbody></table>

#### Networks

La clé `networks` définit les différents réseaux docker de la stack.

```yaml
networks:
  reseau1:
    driver: bridge
  reseau2:
    driver: bridge
    ipam:
      config:
        - subnet: "172.16.0.0/16"
  reseau_externe:
    external: true
    name: mon_reseau_externe
```

<table border="1" id="bkmrk-instruction-descript-1" style="width: 100%; border-collapse: collapse; height: 123.438px;"><thead><tr style="height: 34.1094px;"><th style="width: 18.9511%; height: 34.1094px;">**Instruction**</th><th style="width: 44.4577%; height: 34.1094px;">**Description**</th><th style="width: 36.5912%; height: 34.1094px;">**Exemple**</th></tr></thead><tbody><tr style="height: 34.1094px;"><td style="width: 18.9511%; height: 34.1094px;">`driver`</td><td style="width: 44.4577%; height: 34.1094px;">Type de réseau (`bridge`, `host`, etc.)</td><td style="width: 36.5912%; height: 34.1094px;">`driver: bridge`</td></tr><tr style="height: 55.2188px;"><td style="width: 18.9511%; height: 55.2188px;">`external`</td><td style="width: 44.4577%; height: 55.2188px;">Indique si le réseau a été créé en dehors de la stack docker compose.</td><td style="width: 36.5912%; height: 55.2188px;">`external: true`</td></tr><tr><td style="width: 18.9511%; height: 34.1094px;">`name`</td><td style="width: 44.4577%; height: 34.1094px;">Nom du réseau, utile avec `external`</td><td style="width: 36.5912%; height: 34.1094px;">`name: mon_reseau_externe`

</td></tr><tr><td style="width: 18.9511%;">`ipam`</td><td style="width: 44.4577%;">Configuration IP du réseau</td><td style="width: 36.5912%;">Voir exemple ci-dessus.

</td></tr></tbody></table>

# Docker Compose :  Exemple avec MariaDB

### MariaDB et Adminer

L'image officielle de `MariaDB` indique dans sa documentation un fichier `docker-compose.yml` pour le faire fonctionner avec l'interface Adminer.

[🔗 https://hub.docker.com/\_/mariadb#-via-docker-compose](https://hub.docker.com/_/mariadb#-via-docker-compose)

```yaml
# Use root/example as user/password credentials

services:

  db:
    image: mariadb
    restart: always
    environment:
      MARIADB_ROOT_PASSWORD: example

  adminer:
    image: adminer
    restart: always
    ports:
      - 8080:8080

```

### Exécution

[![image.png](https://formation.tfrichet.fr/uploads/images/gallery/2025-09/scaled-1680-/k3c6eWpWxqgSKZZH-image.png)](https://formation.tfrichet.fr/uploads/images/gallery/2025-09/k3c6eWpWxqgSKZZH-image.png)

[![image.png](https://formation.tfrichet.fr/uploads/images/gallery/2025-09/scaled-1680-/kXIj5Kt4aaGzqXw5-image.png)](https://formation.tfrichet.fr/uploads/images/gallery/2025-09/kXIj5Kt4aaGzqXw5-image.png)

[![image.png](https://formation.tfrichet.fr/uploads/images/gallery/2025-09/scaled-1680-/5La0AjYsR0Ub32vc-image.png)](https://formation.tfrichet.fr/uploads/images/gallery/2025-09/5La0AjYsR0Ub32vc-image.png)

# Docker Swarm

### Introduction

**Docker Swarm** est une surcouche native de Docker pour **orchestrer** un cluster de machines exécutant des conteneurs Docker.

Contrairement à une **exécution locale** de conteneurs, Swarm permet de déployer des conteneurs avec quelques avantages :

- Distribution automatique
- Haute disponibilité
- Scaling automatique
- Redondances
- Etc.

<p class="callout info">Docker Swarm transforme un ensemble de nœuds Docker en un **cluster** unique.</p>

[![logo-docker-swarm-300x296.png](https://formation.tfrichet.fr/uploads/images/gallery/2025-09/scaled-1680-/JEMVeuGr4YlYItuM-logo-docker-swarm-300x296.png)](https://formation.tfrichet.fr/uploads/images/gallery/2025-09/JEMVeuGr4YlYItuM-logo-docker-swarm-300x296.png)

Chaque nœud peut être un **manager** (coordonne le cluster) ou un **worker** (exécute les tâches).  
Les services sont définis avec des règles de réplication, de placement et de mise à jour.

### Exemple

- Nous partons d'une application web hébergée sur **Nginx** avec une exigence de **haute disponibilité** et de **répartition de charge**.
- Nous disposons de plusieurs **instances Docker** (machines hôtes) et nous souhaitons que l'application soit toujours accessible, même si une machine tombe en panne.

```bash
# Sur la machine principale (noeud principal)
docker swarm init

# Sur chaque noeud
docker swarm join IP_NOEUD_PRINCIPAL:2377

# Création du service avec 3 instances
docker service create --name web --replicas 3 -p 80:80 nginx

# Vérifier le déploiement
docker service ls
docker service ps web
```

<p class="callout info">Le service Nginx est maintenant réparti sur plusieurs nœuds, avec équilibrage de charge.</p>

### Différence avec Docker Compose

**Docker Compose** est principalement utilisé pour le développement local ou le déploiement d'application sur une machine unique.  
L'ensemble des services, réseaux et volumes nécessaires sont détaillés dans le fichier `docker-compose.yml`.

**Docker Swarm** est conçu pour le déploiement en production sur plusieurs machines.  
Il offre des fonctionnalités d'orchestration qui ne sont pas disponibles avec **Docker Compose**.

<table border="1" id="bkmrk-fonctionnalit%C3%A9-docke" style="border-collapse: collapse; width: 100%; height: 204.656px;"><colgroup><col style="width: 33.3333%;"></col><col style="width: 33.3333%;"></col><col style="width: 33.3333%;"></col></colgroup><thead><tr style="height: 34.1094px;"><td style="height: 34.1094px;">**Fonctionnalité**</td><td style="height: 34.1094px;">**Docker Compose**</td><td style="height: 34.1094px;">**Docker Swarm**</td></tr></thead><tbody><tr style="height: 34.1094px;"><td style="height: 34.1094px;">**Scope**</td><td style="height: 34.1094px;">Machine locale</td><td style="height: 34.1094px;">Cluster</td></tr><tr style="height: 34.1094px;"><td style="height: 34.1094px;">**Orchestration**</td><td style="height: 34.1094px;">Basique</td><td style="height: 34.1094px;">Avancée</td></tr><tr style="height: 34.1094px;"><td style="height: 34.1094px;">**Scaling**</td><td style="height: 34.1094px;">Non</td><td style="height: 34.1094px;">Oui</td></tr><tr style="height: 34.1094px;"><td style="height: 34.1094px;">**Haute dispo**</td><td style="height: 34.1094px;">Non</td><td style="height: 34.1094px;">Oui</td></tr><tr style="height: 34.1094px;"><td style="height: 34.1094px;">**Load balancing**</td><td style="height: 34.1094px;">Non</td><td style="height: 34.1094px;">Oui</td></tr></tbody></table>

### Docker Secret

**Docker Swarm** propose une fonctionnalité **Docker Secrets** pour stocker et distribuer les données sensibles de manière sécurisée aux services qui en ont besoin.

**Exemple :**

```bash
# Création d'un secret
printf "my super secret password" | docker secret create my_secret -

# Utilisation avec un service
docker service create --name app --secret my_secret alpine:latest sh -c "cat /run/secrets/my_secret"

```

Dans cet exemple, le conteneur `alpine` accède au mot de passe via le fichier `/run/secrets/my_secret`.

Ce fichier est monté automatiquement par **Swarm** et n’est pas visible en dehors du conteneur.

<p class="callout warning">La fonctionnalité Docker Secret n'est disponible qu'avec Docker Swarm.  
Les volumes sont le meilleur moyen de gérer des informations sensible en dehors d'un cluster Docker Swarm.</p>