À 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
ee5456bc4862 nginx:1.23 "/docker-entrypoint.…" 2 seconds ago Up Less than a second 80/tcp my-nginx
d496ee28e239 ghcr.io/ebpro/jupyter-base:quarto-full-fix-chromium-ci-headless-render "bash -lc '\n # [dia…" 3 minutes ago Up 3 minutes amazing_johnson
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
ee5456bc4862 nginx:1.23 "/docker-entrypoint.…" 3 seconds ago Exited (0) Less than a second ago my-nginx
d496ee28e239 ghcr.io/ebpro/jupyter-base:quarto-full-fix-chromium-ci-headless-render "bash -lc '\n # [dia…" 3 minutes ago Up 3 minutes amazing_johnson
#| 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.
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
d496ee28e239 amazing_johnson 0.13% 417.7MiB / 62.66GiB 0.65% 19.7kB / 5.81kB 21.5MB / 81.9MB 57
Réseau
Chaque conteneur a une interface réseau virtuelle et une adresse IP.
Par défaut, les conteneurs sont connectés à un réseau bridge par défaut.
Il est possible de créer des réseaux custom pour isoler les conteneurs.
Les conteneurs sur le même réseau custom peuvent communiquer entre eux par nom (DNS interne).
Réseau par défaut (bridge)
Par défaut, Docker crée un réseau bridge nommé bridge.
#| label: lst-docker-network-lsdocker run -d--name c1 alpine sleep 3600docker run -d--name c2 alpine sleep 3600
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
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
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
b8ffeb8c060c4e42675c414714e25fc72a1cd9d0bd979721e1fb7c068a9ba582
..
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"
e3cffe952ba98fbe08fde24285334a70f6a9f7b63c4604b3a9ba9cd5012835c0
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