Conteneurs: Notions de base (Usage)

Cycle de vie, volumes, réseaux, ressources, bonnes pratiques

Université de Toulon

LIS UMR CNRS 7020

2026-01-20

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.
docker run --rm alpine:3.18 echo "Hello"
Hello

Conteneur en arrière-plan (-d)

  • La commande docker run -d démarre un conteneur en arrière-plan (détaché --detach).
docker run -d --name my-nginx nginx:1.23
30ca0ec083d3e44af2af60b3658de03d63b3a6e678d7abae8fddec3e270ede5d

Liste des conteneurs (ps)

  • La commande docker ps 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.
docker ps
CONTAINER ID   IMAGE        COMMAND                  CREATED        STATUS                  PORTS     NAMES
30ca0ec083d3   nginx:1.23   "/docker-entrypoint.…"   1 second ago   Up Less than a second   80/tcp    my-nginx
9ac1410fec7c   nginx:1.23   "/docker-entrypoint.…"   2 hours ago    Up 2 hours              80/tcp    web

Arrêter (stop) / démarrer (start) / supprimer (rm)

  • 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).
docker stop my-nginx
my-nginx
docker ps -a
CONTAINER ID   IMAGE        COMMAND                  CREATED        STATUS                              PORTS     NAMES
30ca0ec083d3   nginx:1.23   "/docker-entrypoint.…"   1 second ago   Exited (0) Less than a second ago             my-nginx
9ac1410fec7c   nginx:1.23   "/docker-entrypoint.…"   2 hours ago    Up 2 hours                          80/tcp    web
docker start my-nginx
my-nginx
docker stop my-nginx
my-nginx
docker rm my-nginx
my-nginx

Exercice 1 (5 min)

Lancer un conteneur alpine, exécuter uname -a, puis le supprimer.

MY_ALPINE_ID=docker run --name my-alpine alpine:3.18 uname -a
docker rm ${MY_ALPINE_ID}
zsh: command not found: run
docker: 'docker rm' requires at least 1 argument

Usage:  docker rm [OPTIONS] CONTAINER [CONTAINER...]

See 'docker rm --help' for more information
  • L’option --rm peut être utilisée avec docker run pour supprimer automatiquement le conteneur après son arrêt.

Logs, exec et monitoring (10 min)

  • Les logs d’un conteneur sont accessibles via docker logs.
  • L’option -f permet de suivre les logs en temps réel.
docker run --name my-nginx -d nginx:1.23
05fa0eb6fd301b7e1572ead0e83924c3872d422e55e177fe8194b33e78aa3e27
docker logs my-nginx
# docker logs -f my-nginx
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
/docker-entrypoint.sh: Configuration complete; ready for start up
2026/01/19 13:10:31 [notice] 1#1: using the "epoll" event method
2026/01/19 13:10:31 [notice] 1#1: nginx/1.23.4
2026/01/19 13:10:31 [notice] 1#1: built by gcc 10.2.1 20210110 (Debian 10.2.1-6) 
2026/01/19 13:10:31 [notice] 1#1: OS: Linux 6.17.12-300.fc43.x86_64
2026/01/19 13:10:31 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 524288:524288
2026/01/19 13:10:31 [notice] 1#1: start worker processes
2026/01/19 13:10:31 [notice] 1#1: start worker process 29
2026/01/19 13:10:31 [notice] 1#1: start worker process 30
2026/01/19 13:10:31 [notice] 1#1: start worker process 31
2026/01/19 13:10:31 [notice] 1#1: start worker process 32
2026/01/19 13:10:31 [notice] 1#1: start worker process 33
2026/01/19 13:10:31 [notice] 1#1: start worker process 34
2026/01/19 13:10:31 [notice] 1#1: start worker process 35
2026/01/19 13:10:31 [notice] 1#1: start worker process 36
2026/01/19 13:10:31 [notice] 1#1: start worker process 37
2026/01/19 13:10:31 [notice] 1#1: start worker process 38
2026/01/19 13:10:31 [notice] 1#1: start worker process 39
2026/01/19 13:10:31 [notice] 1#1: start worker process 40
2026/01/19 13:10:31 [notice] 1#1: start worker process 41
2026/01/19 13:10:31 [notice] 1#1: start worker process 42
2026/01/19 13:10:31 [notice] 1#1: start worker process 43
2026/01/19 13:10:31 [notice] 1#1: start worker process 44
2026/01/19 13:10:31 [notice] 1#1: start worker process 45
2026/01/19 13:10:31 [notice] 1#1: start worker process 46
2026/01/19 13:10:31 [notice] 1#1: start worker process 47
2026/01/19 13:10:31 [notice] 1#1: start worker process 48
2026/01/19 13:10:31 [notice] 1#1: start worker process 49
2026/01/19 13:10:31 [notice] 1#1: start worker process 50

Exécuter une commande dans un conteneur en cours

  • La commande docker exec permet d’exécuter des commandes dans un conteneur en cours d’exécution.
docker exec my-nginx ls /usr/share/nginx/html
50x.html
index.html
docker stop my-nginx
docker rm my-nginx
my-nginx
my-nginx

Monitoring

  • La commande docker stats affiche l’utilisation des ressources (CPU, mémoire, réseau, disques) des conteneurs en cours d’exécution.
  • L’option --no-stream affiche une seule fois les statistiques.
docker stats --no-stream
CONTAINER ID   NAME      CPU %     MEM USAGE / LIMIT     MEM %     NET I/O           BLOCK I/O    PIDS
9ac1410fec7c   web       0.00%     16.46MiB / 30.79GiB   0.05%     4.73kB / 5.56kB   0B / 360kB   23

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)

docker run -d --name c1 alpine sleep 3600
docker run -d --name c2 alpine sleep 3600
92221deb3643cd76a57dd28a9d2d4057a9fcffe1c1c7cd4764c9172b0d20e96a
1c5d0f913814b459a7110aa7b6d983a11f25d573956a4ec0b304d7d7081c7679

Réseau custom (bonne pratique)

  • Créer un réseau custom avec docker network create.
  • Lancer des conteneurs sur ce réseau avec --network.
docker network create app-net
docker run -d --name web --network app-net nginx:1.23
docker run -d --name db  --network app-net redis:7.0.9
docker run --rm --network app-net curlimages/curl:7.88.1 http://web
Error response from daemon: network with name app-net already exists
docker: Error response from daemon: Conflict. The container name "/web" is already in use by container "9ac1410fec7cde6b9ee85cc7f153f01a29e92ce9c5aa9e92270a44901fdc7d36". You have to remove (or rename) that container to be able to reuse that name.

Run 'docker run --help' for more information
b8706dc1f3308aebd42efaed304d7fcd25d51cc80bdd563ce26d7176a0599779
  % 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   680k      0 --:--:-- --:--:-- --:--:--  600k

Vérifier le réseau (Avancé)

docker network inspect app-net
[
    {
        "Name": "app-net",
        "Id": "07a59e9d4462b3ca6362df7e0436b9aa9b651e3143c21517c9c76b626d18751b",
        "Created": "2026-01-19T11:08:35.751867093Z",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv4": true,
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": {},
            "Config": [
                {
                    "Subnet": "172.18.0.0/16",
                    "IPRange": "",
                    "Gateway": "172.18.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Options": {},
        "Labels": {},
        "Containers": {
            "9ac1410fec7cde6b9ee85cc7f153f01a29e92ce9c5aa9e92270a44901fdc7d36": {
                "Name": "web",
                "EndpointID": "633661e6e8fda5f8b8161a1ccfedea4b3672ec7875b8f7811bf85b71cf306dd7",
                "MacAddress": "fa:4e:15:91:bd:fa",
                "IPv4Address": "172.18.0.2/16",
                "IPv6Address": ""
            },
            "b8706dc1f3308aebd42efaed304d7fcd25d51cc80bdd563ce26d7176a0599779": {
                "Name": "db",
                "EndpointID": "9226f81741135064d2c27e9a1299d5e481a3eea069ba70e4eaf68ce6284fd754",
                "MacAddress": "8e:08:cf:81:69:df",
                "IPv4Address": "172.18.0.3/16",
                "IPv6Address": ""
            }
        },
        "Status": {
            "IPAM": {
                "Subnets": {
                    "172.18.0.0/16": {
                        "IPsInUse": 5,
                        "DynamicIPsAvailable": 65531
                    }
                }
            }
        }
    }
]

Communication conteneur → conteneur

docker run --rm --network app-net curlimages/curl:7.88.1 http://web
  % 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   817k      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.

Port mapping

  • Chaque conteneur a son propre espace réseau isolé.
  • Il peut 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 -p hôte:conteneur.
  • Cela mappe un port de l’hôte vers un port du conteneur.
    • Ex: -p 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.
docker run -d --name web-port -p 8080:80 nginx:1.23
eab00b80784791ce38b29bdd37aaa069e117b7f1b7ff85e1b4da78e72915e5ec

Tester depuis l’hôte

  • Il est possible de tester l’accès au service exposé depuis l’hôte avec curl ou un navigateur.
  • Avec l’adresse http://localhost:8080 si Docker est natif.
  • Avec l’adresse IP de la VM si Docker tourne dans une VM (ex: WSL, Docker Desktop).
  • A moins que Docker soit configuré pour exposer les ports vers l’hôte directement.
curl -I http://localhost:8080
curl: (7) Failed to connect to localhost port 8080 after 0 ms: Couldn't connect to server

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.
docker run --rm --add-host=host.docker.internal:host-gateway \
    curlimages/curl:7.88.1 http://host.docker.internal:8080
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0100   615  100   615    0     0   632k      0 --:--:-- --:--:-- --:--:--  600k
<!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>

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 -v 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.

Bind mount (risky, dev)

#| output: true
#| echo: true
mkdir -p /tmp/myapp
echo "hello" > /tmp/myapp/index.html

docker run --rm -v /tmp/myapp:/usr/share/nginx/html \
  -p 8081:80 nginx:1.23

Volume nommé (better, prod)

docker volume create pgdata

docker run -d --name db \
  -v pgdata:/var/lib/postgresql/data \
  -e POSTGRES_PASSWORD=secret \
  postgres:15.2
pgdata
docker: Error response from daemon: Conflict. The container name "/db" is already in use by container "b8706dc1f3308aebd42efaed304d7fcd25d51cc80bdd563ce26d7176a0599779". You have to remove (or rename) that container to be able to reuse that name.

Run 'docker run --help' for more information

Exercice de persistance

  • lancer Postgres sans volume → perdre les données
  • relancer avec volume → conserver les données

Hint: regarder la page officielle Postgres Docker Hub pour le chemin des données. (see https://hub.docker.com/_/postgres)

Exercice 3 (10 min)

Lancer un conteneur Postgres avec volume, créer une table, arrêter et relancer, puis vérifier que les données sont toujours présentes.

Gestion des ressources (Avancé)

  • Docker permet de limiter les ressources utilisées par un conteneur.
  • Cela inclut la mémoire, le CPU, et le nombre de PIDs.

Limiter CPU / mémoire / PIDs

docker run -d --name webapp \
  --memory="512m" \
  --cpus="0.5" \
  --pids-limit=50 \
  nginx:alpine
47a7552536050532ada6091792bb6d67ff1a5adf53689518abb2fc768c73087e

Modifier les limites d’un conteneur existant

  • Il est possible de modifier les limites d’un conteneur en cours d’exécution avec docker update.
docker update --memory="1g" --cpus="1" webapp
webapp

Bonnes pratiques d’usage

  • Nettoyage régulier
docker container prune --force
docker image prune --force
docker volume prune --force
Total reclaimed space: 0B
Total reclaimed space: 0B
Deleted Volumes:
bf0826de7e362d8adb399bd6472ffca675d548dc5c740591578f486a6d087a49

Total reclaimed space: 88B
  • É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

Objectif

Assembler une application simple avec :

  • un serveur web
  • une base de données
  • un réseau custom
  • un volume

Exemple

  • nginx + postgres sur app-net

  • nginx expose un service web

  • postgres stocke les données dans un volume

  • Nettoyage final

docker stop web-port webapp db
docker rm web-port webapp db
docker network rm app-net
docker volume rm pgdata
web-port
webapp
db
web-port
webapp
db
Error response from daemon: error while removing network: network app-net has active endpoints (name:"web" id:"633661e6e8fd")
exit status 1
pgdata