Ce support a été généré par Quarto : les cellules sont exécutées par le noyau indiqué lors du rendu.
Certaines parties ont été rédigées avec l’assistance d’un modèle de langage ; le contenu a été relu et validé par l’auteur.
Objectifs du cours
À l’issue de ce cours, vous devez être capables de :
Manipuler des conteneurs Docker (run, stop, start, rm)
Utiliser les volumes pour la persistance
Connecter des conteneurs via des réseaux
Exposer des ports vers l’hôte
Gérer les ressources d’un conteneur (CPU / RAM / PIDs)
Comprendre et appliquer les bonnes pratiques d’usage
Rappel rapide
Un conteneur est une instance d’une image. L’image est un artefact immuable (voir le cours “Image”). Le conteneur est un processus isolé qui tourne sur le noyau de l’hôte.
Astuce
Image = template immuable
Conteneur = exécution d’un processus isolé
Les images
Une image contient tout le nécessaire pour exécuter une application (code, runtime, bibliothèques, dépendances, fichiers de configuration).
Chaque couche est en lecture seule, sauf la dernière (couche de conteneur).
Les images sont stockées dans des registres (Docker Hub, GitHub Container Registry, etc.).
Les images sont identifiées par un nom : [registre/]nom[:tag] (ex: docker.io/library/nginx:1.23).
Si le registre n’est pas spécifié, Docker Hub est utilisé par défaut.
Si le tag n’est pas spécifié, latest est utilisé par défaut.
Une image peut être référencée par son digest (SHA256).
Une image peut avoir plusieurs tags.
Ex: nginx:1, nginx:1.29,nginx:mainline, nginx:latest, nginx:f3524ef8b874 peuvent référencer la même image.
Une image est destinée à une plateforme spécifique (architecture CPU, OS).
Ex: linux/amd64, linux/arm64/v8, windows/amd64.
Il est possible de créer des images multi-plateformes (multi-arch).
Cycle de vie d’un conteneur
Exécution simple
La commande docker run crée et démarre un conteneur à partir d’une image.
Le conteneur s’arrête lorsque le processus principal se termine (--rm pour supprimer automatiquement après).
Le format général est docker run [OPTIONS] IMAGE [COMMAND] [ARG...].
Si l’image n’est pas présente localement, Docker la télécharge depuis le registre.
#| label: lst-run-alpine#| title: "Premier conteneur Docker"#| caption: "Exécution d’un conteneur Alpine jetable affichant un message"#| code-fold: true#| code-summary: "Afficher la commande Docker"#| collapse: truedocker run --rm alpine:3.18 echo "Hello"
La commande docker run -d démarre un conteneur en arrière-plan (détaché --detach).
#| label: lst-run-nginx#| title: "Conteneur Nginx en arrière-plan"#| caption: "Démarrage d’un conteneur Nginx en arrière-plan"docker run -d--name my-nginx nginx:1.23
La commande docker ps (docker container ls) liste les conteneurs en cours d’exécution.
L’option -a liste tous les conteneurs (y compris arrêtés).
Chaque conteneur a un ID unique, un nom, un statut, des ports exposés, etc.
#| label: lst-docker-ps#| title: "Liste des conteneurs en cours d’exécution"#| caption: "Affichage des conteneurs Docker en cours d’exécution"docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
610c598659a1 nginx:1.23 "/docker-entrypoint.…" 1 second ago Up Less than a second 80/tcp my-nginx
Le cycle de vie d’un conteneur inclut les états : démarré, arrêté, supprimé.
Lorsqu’un conteneur est arrêté, il reste sur le système jusqu’à sa suppression (rm).
#| label: lst-stop-nginxdocker stop my-nginx
my-nginx
#| label: lst-docker-ps-alldocker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
610c598659a1 nginx:1.23 "/docker-entrypoint.…" 2 seconds ago Exited (0) Less than a second ago my-nginx
#| label: lst-start-nginxdocker start my-nginx
my-nginx
#| label: lst-rm-nginxdocker stop my-nginx
my-nginx
#| label: lst-rm-nginxdocker rm my-nginx
my-nginx
Exercice 1
Lancer un conteneur alpine, exécuter uname -a, puis le supprimer.
Créer un réseau custom avec docker network create.
Lancer des conteneurs (un serveur web, une base de données) sur ce réseau avec --network.
#| label: lst-docker-network-createdocker network create app-netdocker run --detach--name web \--network app-net \ nginx:1.23docker run --detach--name db \--network app-net \ redis:7.0.9
#| label: lst-docker-ping-successdocker run --rm--network app-net curlimages/curl:7.88.1 http://web
Unable to find image 'curlimages/curl:7.88.1' locally
7.88.1: Pulling from curlimages/curl
d4c655b444ee: Pulling fs layer
b0ca1de0cc3e: Pulling fs layer
659101a913e8: Pulling fs layer
ea634e3b33ec: Pulling fs layer
8d68097e7e08: Pulling fs layer
473ceed980f8: Pulling fs layer
213ec9aee27d: Pulling fs layer
55bfd993f83a: Pulling fs layer
4c26da78c210: Pulling fs layer
df0b84b7230e: Pulling fs layer
45477c99a790: Pulling fs layer
142e60c0dead: Downloading 1.178kB/1.178kBd4c655b444ee: Downloading 291B/291B142e60c0dead: Download complete b0ca1de0cc3e: Download complete d4c655b444ee: Download complete 659101a913e8: Download complete 8d68097e7e08: Download complete ea634e3b33ec: Downloading 4.396MB/4.396MB473ceed980f8: Download complete 45477c99a790: Download complete 55bfd993f83a: Download complete ea634e3b33ec: Download complete 4c26da78c210: Download complete 213ec9aee27d: Downloading 2.097MB/2.806MBdf0b84b7230e: Download complete 213ec9aee27d: Download complete 213ec9aee27d: Extracting 1 sea634e3b33ec: Extracting 1 s213ec9aee27d: Pull complete ea634e3b33ec: Extracting 1 sea634e3b33ec: Extracting 1 s55bfd993f83a: Extracting 1 sea634e3b33ec: Pull complete 55bfd993f83a: Pull complete df0b84b7230e: Extracting 1 s8d68097e7e08: Extracting 1 sdf0b84b7230e: Pull complete 8d68097e7e08: Pull complete 4c26da78c210: Extracting 1 s659101a913e8: Extracting 1 s4c26da78c210: Pull complete 659101a913e8: Pull complete 473ceed980f8: Extracting 1 sb0ca1de0cc3e: Extracting 1 s473ceed980f8: Pull complete b0ca1de0cc3e: Pull complete 45477c99a790: Extracting 1 sd4c655b444ee: Extracting 1 s45477c99a790: Pull complete d4c655b444ee: Pull complete Digest: sha256:48318407b8d98e8c7d5bd4741c88e8e1a5442de660b47f63ba656e5c910bc3da
Status: Downloaded newer image for curlimages/curl:7.88.1
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
100 615 100 615 0 0 724k 0 --:--:-- --:--:-- --:--:-- 600k
Exercice 2 (Max 10 min)
Créer un réseau mynet, lancer deux conteneurs alpine nommés a1 et a2 sur ce réseau, puis vérifier qu’ils se ping par nom en exécutant une commande dans un conteneur en mode non-interactif.
Port mapping
Chaque conteneur a son propre espace réseau isolé.
Ils peuvent donc utiliser les mêmes ports en interne sans conflit.
Ex: plusieurs conteneurs postgres peuvent écouter sur leur port 5432.
Pour exposer un service d’un conteneur vers l’hôte, utiliser --publish hôte:conteneur.
Cela mappe un port de l’hôte vers un port du conteneur.
Ex: --publish 8080:80 mappe le port 8080 de l’hôte vers le port 80 du conteneur.
Attention aux conflits de ports sur l’hôte.
Attention si docker tourne dans une VM (WSL, Docker Desktop) : le port est exposé sur la VM, pas directement sur l’hôte.
Le port mapping est une règle de NAT entre l’hôte et le réseau bridge Docker.
#| label: lst-docker-port-mappingdocker run --detach--name web-port-1 --publish 8080:80 nginx:1.28docker run --detach--name web-port-2 --publish 8081:80 nginx:1.23
HTTP/1.1 200 OK
Server: nginx/1.28.1
Date: Wed, 28 Jan 2026 12:50:48 GMT
Content-Type: text/html
Content-Length: 615
Last-Modified: Tue, 23 Dec 2025 18:40:33 GMT
Connection: keep-alive
ETag: "694ae221-267"
Accept-Ranges: bytes
HTTP/1.1 200 OK
Server: nginx/1.23.4
Date: Wed, 28 Jan 2026 12:50:48 GMT
Content-Type: text/html
Content-Length: 615
Last-Modified: Tue, 28 Mar 2023 15:01:54 GMT
Connection: keep-alive
ETag: "64230162-267"
Accept-Ranges: bytes
Accès conteneur -> hôte
Il est possible d’accéder à un service exposé sur l’hôte depuis un conteneur.
Utiliser host.docker.internal comme nom d’hôte.
ATTENTION : host.docker.internal fonctionne nativement sur Docker Desktop (Windows/Mac) et Docker sous Linux (depuis Docker 20.10) mais n’est standardisé que sur Docker Desktop.
docker run --rm\ curlimages/curl:7.88.1 http://host.docker.internal:8080
Volumes : persistance
Les données dans un conteneur sont éphémères : elles disparaissent à l’arrêt/suppression du conteneur.
Pour persister les données, utiliser des volumes.
Un volume est un espace de stockage géré par Docker, indépendant du cycle de vie des conteneurs.
Les volumes peuvent être montés dans un conteneur avec --volume volume:chemin_dans_conteneur.
Il existe deux types de volumes :
Bind mount : mappe un répertoire de l’hôte vers un répertoire du conteneur.
Volume nommé : volume géré par Docker, stocké dans l’espace de stockage Docker.
Astuce
bind mount = couplage fort hôte ↔︎ conteneur
volume nommé = abstraction portable
Bind mount (risky, dev)
montage avec --volume /chemin/host:/chemin/conteneur
#| output: true#| echo: true#| label: lst-bind-mount-nginxecho"hello">${TP_DIR}/www/index.htmldocker run --rm-v${TP_DIR}/www:/usr/share/nginx/html \-p 8081:80 nginx:1.23- Utile en développement pour monter du code source.- Permet d’éditer les fichiers sur l’hôte et de les voir dans le conteneur.- Dangers :- les permissions peuvent poser problème selon l’OS hôte- les performances peuvent être dégradées (ex: Docker Desktop sur Windows/Mac)- le conteneur ne peut pas être déplacé facilement (dépendance à l’hôte)
Volume nommé (better, prod)
montage avec --volume nom_volume:chemin/conteneur
#| label: lst-named-volume-postgresIMAGE="postgres:15.2"VOLUME="pgdata"PW="secret"docker volume create "$VOLUME"# Initialisation de la base et création d'une tabledocker run -d--name db \-v"$VOLUME":/var/lib/postgresql/data \-e POSTGRES_PASSWORD="$PW"\"$IMAGE"# Attendre que la base soit prêtetimeout 60 sh -c'until docker exec db pg_isready -U postgres >/dev/null 2>&1; do echo -n '.'; sleep 1; done'||(echo"Postgres not ready after 60s">&2;exit 1)# Créer une table et insérer des données depuis le conteneur de la basedocker exec db psql -U postgres -c"CREATE TABLE test (id SERIAL PRIMARY KEY, name VARCHAR(50));"docker exec db psql -U postgres -c"INSERT INTO test (name) VALUES ('Alice'), ('Bob');"# Arrêter et supprimer le conteneur mais conserver le volumedocker stop db &&docker rm db
pgdata
99085d8ce20b237fd98be7cd6c85c1124f7811da60449372e145b81c2fbbac84
.......
CREATE TABLE
INSERT 0 2
db
db
#| label: lst-verify-named-volume# Redémarrer un nouveau conteneur avec le même volumedocker run -d--name db2 \-v"$VOLUME":/var/lib/postgresql/data \-e POSTGRES_PASSWORD="$PW"\"$IMAGE"# Attendre que la base soit prêtetimeout 60 sh -c'until docker exec db2 pg_isready -U postgres >/dev/null 2>&1; do echo -n '.'; sleep 1; done'||(echo"Postgres not ready after 60s">&2;exit 1)# Vérifier que les données persistentdocker exec db2 psql -U postgres -c"SELECT * FROM test;"# Nettoyagedocker stop db2 &&docker rm db2docker volume rm "$VOLUME"
4464cf359a6bffb9c7191c968508ffb6687d99e7e1c90698ce6d1656ab4a7496
id | name
----+-------
1 | Alice
2 | Bob
(2 rows)
db2
db2
pgdata
Exercice de persistance
lancer MySQL sans volume → perdre les données
relancer avec volume → conserver les données
Hint: l’image officielle MySQL stocke les données dans /var/lib/mysql. Voir la page Docker Hub : https://hub.docker.com/_/mysql
Exercice 3 (10 min)
Démarrer MySQL au premier plan, observer l’initialisation (création de la base), arrêter avec Ctrl‑C, puis relancer le même volume et constater qu’il n’y a plus d’initialisation.
Consignes :
Lisez la page Docker Hub de MySQL pour comprendre
les variables d’environnement essentielles (notamment MYSQL_ROOT_PASSWORD, MYSQL_DATABASE) : https://hub.docker.com/_/mysql
le chemmin de stockage des données dans le conteneur (/var/lib/mysql)
Créez un volume nommé mysql-data pour stocker les données persistantes.
Démarrez MySQL en mode attaché (sans --detach
#| label: lst-mysql-persistence# create a named volume for MySQL data (one-time)docker volume create mysql-dataexportMYSQL_ROOT_PASSWORD=secretpwexportMYSQL_DATABASE=demo# Run MySQL in the foreground so you see initialization logsdocker run --name mysql-db --detach\--env MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}\--env MYSQL_DATABASE=${MYSQL_DATABASE}\--volume mysql-data:/var/lib/mysql \ mysql:8.0# Wait for MySQL to be readytimeout 60 sh -c'until docker exec mysql-db mysqladmin ping -h "localhost" --silent >/dev/null 2>&1; do echo -n "."; sleep 1; done'||(echo"MySQL not ready after 60s">&2;exit 1)# Stop and remove the container to simulate a restartdocker stop mysql-db &&docker rm mysql-db# Start a new container with the same volume to see data persistence.docker run --name mysql-db --detach\--env MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}\--env MYSQL_DATABASE=${MYSQL_DATABASE}\--volume mysql-data:/var/lib/mysql \ mysql:8.0# Wait for MySQL to be ready againtimeout 60 sh -c'until docker exec mysql-db mysqladmin ping -h "localhost" --silent >/dev/null 2>&1; do echo -n "."; sleep 1; done'||(echo"MySQL not ready after 60s">&2;exit 1)# Check the logs to confirm no re-initializationdocker logs mysql-db|head-n 20
Il est possible de modifier les limites d’un conteneur en cours d’exécution avec docker update.
docker update --memory="1g"--cpus="1" cpu-test
Bonnes pratiques d’usage
Nettoyage régulier
docker container prunedocker image prunedocker volume prunedocker network prunedocker system prune --all# NETTOYAGE COMPLET (ATTENTION : SUPPRIME TOUT)# Y COMPRIS LES DONNESS DANS DES VOLUMES !docker system prune --all--volumes--force# RISQUE
Éviter latest en production
Utiliser des tags de version stables.
Ne pas stocker de secrets dans l’environnement
Préférer --secret (ou mécanismes externes).
Logs sur stdout/stderr
Les conteneurs doivent produire des logs vers la sortie standard.
Atelier final
Déployer une application simple composée de deux conteneurs :
une application Java accédant à une base de données via JDBC
une base de données PostgreSQL
Contraintes :
chaque composant doit être dans son propre conteneur
les deux conteneurs doivent communiquer via un réseau Docker personnalisé
les données de la base doivent être persistées dans un volume Docker
l’application Java doit se connecter à la base par le nom du conteneur, pas par localhost
Objectif :
vérifier la communication entre conteneurs, l’usage des réseaux et la persistance des données.
Conclusion
ImportantA Retenir
Un conteneur est une instance d’une image, exécutant un processus isolé.
Le cycle de vie d’un conteneur inclut les états : démarré, arrêté, supprimé.
Les conteneurs peuvent être connectés via des réseaux custom pour la communication.
Les volumes permettent de persister les données indépendamment du cycle de vie des conteneurs.
Il est possible de limiter les ressources (CPU, mémoire) utilisées par un conteneur.
Appliquer les bonnes pratiques d’usage pour une gestion efficace des conteneurs.
Gérer les conteneurs manuellement n’est pas viable en production : il faut automatiser