Aller au contenu
Pourquoi les conteneurs existent

Pourquoi les conteneurs existent

100 · Comprendre ⏱ 35 min dockerlinux

À la fin, vous saurez

  • Décrire les trois problèmes concrets que résout un conteneur : dépendances, dérive de configuration et densité
  • Distinguer une image d'un conteneur
  • Comparer un conteneur et une machine virtuelle sur l'isolation, le noyau, le démarrage et la sécurité
  • Situer Docker dans l'écosystème : OCI, containerd, Podman, Kubernetes
  • Identifier les cas où un conteneur n'est pas le bon outil

Prérequis

Testé avec docker 29.8.1 noyau 7.0 ubuntu 24.04 , vérifié le 1 octobre 2026

Pourquoi

Prenons une situation que toute équipe a vécue. Un client vous confie une application interne écrite en Python 3.9, qui dépend d'une version précise de la bibliothèque cliente PostgreSQL et d'un outil de génération de PDF installé par le paquet système wkhtmltopdf. Le développeur qui l'a écrite est parti. Le serveur de recette tourne sous Ubuntu 24.04, qui fournit Python 3.12. Le paquet wkhtmltopdf n'y est plus maintenu. Sur le serveur de production, quelqu'un a installé à la main, il y a trois ans, une bibliothèque dans /usr/local/lib que personne n'a documentée.

Trois problèmes distincts se cachent derrière le célèbre « ça marche sur ma machine » :

  1. Les dépendances. Une application ne se résume pas à son code : elle a besoin d'un interpréteur, de bibliothèques système, de fichiers de configuration, de certificats, parfois de polices de caractères. Deux applications sur la même machine peuvent exiger des versions incompatibles de la même bibliothèque.
  2. La dérive de configuration. Un serveur installé à la main s'éloigne jour après jour de sa description. Chaque correctif urgent, chaque apt upgrade, chaque fichier modifié « juste pour tester » crée un écart. Au bout d'un an, plus personne ne sait reconstruire la machine à l'identique.
  3. La densité et la vitesse. La réponse classique aux deux premiers problèmes est « une application, une machine virtuelle ». Elle fonctionne, mais chaque VM embarque un système d'exploitation complet, démarre en dizaines de secondes, consomme de la mémoire pour son propre noyau et doit être mise à jour séparément.

Un conteneur répond aux trois à la fois : il emballe l'application avec tout son système de fichiers (interpréteur, bibliothèques, configuration) dans un artefact immuable, l'image, et l'exécute comme un processus isolé qui partage le noyau de la machine hôte. On livre alors l'image, pas une procédure d'installation. Ce qui a été testé est exactement ce qui tourne en production.

Sans conteneurs, on retombe sur des procédures d'installation de vingt pages, des environnements de recette qui ne ressemblent jamais à la production et des montées de version repoussées pendant des années parce que « tout est imbriqué ».

Les concepts

Image et conteneur

Deux mots reviennent sans cesse, et il faut les séparer dès le départ.

  • Une image est un modèle figé : une pile de systèmes de fichiers en lecture seule, plus des métadonnées (la commande à lancer par défaut, les variables d'environnement, l'utilisateur, les ports documentés). Une image ne « tourne » pas. Elle se stocke, se versionne, se transporte.
  • Un conteneur est une instance en cours d'exécution d'une image : un ou plusieurs processus, isolés, auxquels on a ajouté une fine couche de système de fichiers inscriptible.

L'analogie la plus juste est celle que vous connaissez déjà sous Linux : un programme et un processus. /usr/bin/python3 est un fichier sur le disque ; chaque fois que vous le lancez, vous obtenez un nouveau processus, avec sa propre mémoire. De la même manière, l'image python:3.9-slim est stockée une seule fois, et vous pouvez en démarrer autant de conteneurs que vous voulez, chacun avec son propre état.

Conteneur ou machine virtuelle

Une machine virtuelle émule du matériel. Un hyperviseur (KVM, VMware ESXi, Hyper-V) présente à chaque VM un processeur, de la mémoire, des disques et des cartes réseau virtuels ; la VM y installe son propre noyau et son propre système.

Un conteneur ne virtualise rien. Il demande au noyau de l'hôte de restreindre ce qu'un processus voit et ce qu'il peut consommer. Le processus tourne directement sur le noyau de l'hôte, comme n'importe quel autre processus, mais il voit son propre arbre de processus, sa propre pile réseau, son propre système de fichiers racine.

    flowchart TB
  subgraph VM["Machines virtuelles"]
    direction TB
    A1["App A"] --- OS1["Noyau + OS invité A"]
    A2["App B"] --- OS2["Noyau + OS invité B"]
    OS1 --- H["Hyperviseur"]
    OS2 --- H
    H --- HW1["Matériel"]
  end
  subgraph CT["Conteneurs"]
    direction TB
    C1["App A + ses bibliothèques"] --- K["Noyau Linux de l'hôte"]
    C2["App B + ses bibliothèques"] --- K
    K --- HW2["Matériel"]
  end
  

Les conséquences pratiques de cette différence se résument ainsi :

Machine virtuelleConteneur
Ce qui est isoléUn ordinateur entierUn groupe de processus
NoyauUn par VMCelui de l'hôte, partagé
DémarrageDizaines de secondes (amorçage d'un OS)Fraction de seconde (lancement d'un processus)
Empreinte mémoire minimaleCentaines de MoCelle du processus lui-même
Systèmes invités possiblesN'importe lequel (Windows sur Linux, etc.)Même famille de noyau que l'hôte
Frontière de sécuritéForte : l'hyperviseur, surface réduitePlus faible : toute l'interface du noyau

La dernière ligne est essentielle et nous y reviendrons dans la partie Sécurité. Les deux techniques ne s'opposent pas : en pratique, la plupart des conteneurs en production tournent dans des machines virtuelles. Un cluster Kubernetes managé comme Kapsule chez Scaleway, par exemple, exécute vos conteneurs sur des instances qui sont elles-mêmes des VM.

Ce qui rend l'isolation possible

Le noyau Linux fournit deux familles de mécanismes, que la leçon suivante démonte une à une :

  • Les namespaces (espaces de noms) limitent ce qu'un processus voit : les autres processus (PID), les interfaces réseau (net), les points de montage (mnt), le nom d'hôte (uts), les files de messages partagées (ipc), les identifiants d'utilisateurs (user), la hiérarchie de cgroups (cgroup) et certaines horloges (time).
  • Les cgroups (groupes de contrôle) limitent ce qu'un processus consomme : mémoire, temps processeur, entrées-sorties disque, nombre de processus.

S'y ajoutent des mécanismes de restriction des privilèges : les capabilities Linux, les filtres d'appels système seccomp, et les modules de sécurité comme AppArmor ou SELinux.

Retenez dès maintenant la phrase qui résume tout ce cours : un conteneur est un processus Linux ordinaire, lancé avec des namespaces, des cgroups et des restrictions de privilèges, sur un système de fichiers racine fourni par une image. Il n'existe pas d'objet « conteneur » dans le noyau.

Une brève histoire

Les conteneurs ne sont pas nés avec Docker. Ils sont le résultat d'une lente accumulation de mécanismes d'isolation :

AnnéeÉtapeCe qu'elle apporte
1979chroot dans Unix V7Changer la racine du système de fichiers vue par un processus
2000Jails de FreeBSD 4.0Isoler processus, réseau et utilisateur root dans une « prison »
2002Premier namespace Linux (mount)Points de montage propres à un groupe de processus
2004-2005Zones de Solaris, OpenVZConteneurs « système » complets en production
2008cgroups dans Linux 2.6.24, puis LXCLimitation des ressources ; premier outillage de conteneurs sous Linux sans correctif du noyau
2013DockerFormat d'image, registre public, outillage simple
2015Open Container InitiativeStandardisation du format d'image et de l'exécution
2015-2017runc (2015) puis containerd (2016-2017) séparés de DockerLe moteur devient un assemblage de briques réutilisables
2022Kubernetes 1.24 retire dockershimKubernetes parle directement à containerd ou CRI-O

L'article de 2000 sur les jails de FreeBSD posait déjà le problème dans les termes actuels : comment donner à un administrateur « root » les pleins pouvoirs dans son environnement sans lui donner ceux de la machine entière.

Ce que Docker a apporté en 2013 n'est donc pas l'isolation, qui existait, mais l'emballage : un format d'image en couches, une commande docker run qui cache toute la complexité, et un registre public (Docker Hub) où partager les images. Lors de sa présentation éclair à PyCon en mars 2013, Solomon Hykes montrait surtout cela : on n'installe plus un logiciel, on le lance.

Docker, OCI, containerd : qui fait quoi

Le mot « Docker » désigne aujourd'hui plusieurs choses qu'il vaut mieux distinguer :

  • Docker Inc., l'entreprise, qui édite Docker Desktop et opère Docker Hub.
  • Docker Engine, le moteur libre (projet Moby) : le démon dockerd et le client docker. C'est lui que ce cours utilise.
  • Le format d'image, devenu un standard ouvert de l'Open Container Initiative (OCI) sous le nom OCI Image Format.

L'OCI, fondée en 2015 sous l'égide de la Linux Foundation, publie trois spécifications : le format d'image (image-spec), le format d'exécution (runtime-spec, implémenté notamment par runc) et le protocole des registres (distribution-spec). Grâce à elles, une image construite avec Docker s'exécute avec Podman, containerd ou Kubernetes, et inversement. Apprendre Docker, c'est donc apprendre des concepts qui valent pour tout l'écosystème.

En pratique

Les commandes qui suivent supposent Docker déjà installé ; l'installation propre fait l'objet de la leçon 4. Si vous n'avez pas encore Docker, lisez cette partie pour l'intuition et revenez la rejouer plus tard.

Deux versions de Python côte à côte

L'hôte de test fournit Python 3.12 :

$ python3 --version
Python 3.12.3

Sans rien installer sur l'hôte, lançons l'ancienne version exigée par l'application du client, puis une version très récente :

$ docker run --rm python:3.9-slim python -c 'import sys; print(sys.version)'
3.9.25 (main, Oct 31 2025, 23:16:49)
[GCC 14.2.0]
$ docker run --rm python:3.14-slim python -c 'import sys; print(sys.version)'
3.14.7 (main, Sep 19 2026, 01:01:59) [GCC 14.2.0]

Décomposons la première commande :

  • docker run : créer un conteneur à partir d'une image et le démarrer.
  • --rm : supprimer le conteneur dès qu'il se termine. Sans cette option, chaque essai laisse un conteneur arrêté derrière lui.
  • python:3.9-slim : l'image, au format nom:étiquette. slim désigne une variante réduite, basée sur Debian.
  • Le reste (python -c '...') est la commande exécutée dans le conteneur, à la place de la commande par défaut de l'image.

Trois interpréteurs Python cohabitent maintenant sur la machine sans le moindre conflit, et l'hôte n'a pas été modifié : rien dans /usr/bin, rien dans /usr/lib.

Le noyau est partagé

Lançons un conteneur Alpine Linux, une distribution minimaliste, et demandons-lui qui il est :

$ docker run --rm alpine:3.22 cat /etc/os-release
NAME="Alpine Linux"
ID=alpine
VERSION_ID=3.22.6
PRETTY_NAME="Alpine Linux v3.22"
HOME_URL="https://alpinelinux.org/"
BUG_REPORT_URL="https://gitlab.alpinelinux.org/alpine/aports/-/issues"
$ docker run --rm alpine:3.22 uname -r
7.0.0-34-generic

Le système de fichiers est celui d'Alpine, mais le noyau est celui de l'hôte Ubuntu : 7.0.0-34-generic. Il n'y a pas de noyau Alpine ici. Une « image Alpine » ou une « image Debian » ne contient que l'espace utilisateur de ces distributions : leurs bibliothèques, leurs outils, leur gestionnaire de paquets.

Démarrer, c'est lancer un processus

Le premier lancement d'une image la télécharge, ce qui prend quelques secondes :

$ time docker run --rm alpine:3.22 echo bonjour
Unable to find image 'alpine:3.22' locally
3.22: Pulling from library/alpine
53f8f5e03afd: Pulling fs layer
53f8f5e03afd: Download complete
b2e1ce860133: Download complete
0629b44bdb87: Download complete
53f8f5e03afd: Pull complete
Digest: sha256:5291449c3df73caf6ed85e649dec1b9e818b39a5d8c871e97afc13e9cd5e8fa8
Status: Downloaded newer image for alpine:3.22
bonjour

real	0m2,318s
user	0m0,031s
sys	0m0,030s

Retenez l'empreinte sha256:5291449c... et la ligne 53f8f5e03afd : la leçon 7 les retrouvera en démontant l'image octet par octet. Les lancements suivants sont presque instantanés :

$ for i in 1 2 3; do /usr/bin/time -f "%e s" docker run --rm alpine:3.22 true; done
0.47 s
0.45 s
0.41 s

En moins d'une demi-seconde, Docker a créé un conteneur, préparé son système de fichiers, configuré son réseau, lancé la commande true, attendu sa fin et tout supprimé. Une machine virtuelle aurait encore été en train d'initialiser son BIOS virtuel.

Le conteneur vu depuis l'hôte

Démarrons un conteneur qui ne fait que dormir, en arrière-plan (-d, pour detached) et avec un nom lisible :

$ docker run -d --name dormeur alpine:3.22 sleep 1000
101c3b95305fbaaa319cead50129bd45cd959826eee702ddd1fbb480a9bd7831

La longue chaîne affichée est l'identifiant du conteneur. Cherchons maintenant ce processus depuis l'hôte, avec le ps habituel :

$ ps -o pid,ppid,user,cmd -C sleep
    PID    PPID USER     CMD
1614374 1614349 root     sleep 1000

Il est là, dans la table des processus de l'hôte, avec un PID ordinaire. Son parent est un petit programme chargé de le surveiller :

$ ps -o pid,cmd -p 1614349
    PID CMD
1614349 /usr/bin/containerd-shim-runc-v2 -namespace moby -id 101c3b95305f... -address /run/containerd/containerd.sock

Regardons maintenant la même chose depuis l'intérieur du conteneur :

$ docker exec dormeur ps
PID   USER     TIME  COMMAND
    1 root      0:00 sleep 1000
    7 root      0:00 ps

À l'intérieur, sleep est le processus numéro 1 et ne voit que lui-même (et le ps que nous venons de lancer). À l'extérieur, c'est le processus 1614374 parmi des centaines d'autres. C'est le même processus, vu à travers deux espaces de noms de PID différents. Faites le ménage :

$ docker rm -f dormeur
dormeur

Sous le capot

Que s'est-il passé lors du docker run -d --name dormeur alpine:3.22 sleep 1000 ? En simplifiant (la leçon 3 détaille chaque étape) :

  1. Le client docker envoie une requête HTTP au démon dockerd par le socket Unix /var/run/docker.sock.
  2. dockerd vérifie que l'image alpine:3.22 est présente localement, la télécharge sinon, et demande à containerd de créer un conteneur.
  3. containerd prépare le système de fichiers racine en empilant les couches de l'image et une couche inscriptible (overlay), puis rédige une description d'exécution au format OCI : un fichier config.json.
  4. containerd lance un shim, containerd-shim-runc-v2, qui appelle runc. runc lit config.json, crée les namespaces, place le processus dans un cgroup, applique les restrictions de sécurité, change la racine du système de fichiers et exécute sleep 1000.
  5. runc se termine aussitôt. Le shim reste le parent du processus : il garde ses entrées-sorties et récupère son code de sortie, ce qui permet de redémarrer containerd sans tuer les conteneurs (et dockerd aussi, si l'option live-restore est activée, voir leçon 3).

C'est pourquoi ps montre containerd-shim-runc-v2 comme parent de sleep, et pourquoi aucun processus nommé « docker » n'apparaît dans la filiation.

Pièges courants

Traiter un conteneur comme une petite VM. On y installe un serveur SSH, on s'y connecte pour modifier des fichiers, on y lance plusieurs services avec un superviseur. Tout ce qui est modifié dans un conteneur disparaît avec lui. La bonne pratique est inverse : un conteneur est jetable et remplaçable ; on modifie l'image, pas le conteneur.

Oublier que le noyau est partagé. Une image construite pour un noyau récent peut échouer sur un hôte ancien si elle utilise un appel système absent. Une image Windows ne peut pas tourner sur un hôte Linux. Et une application qui a besoin d'un module noyau particulier doit le trouver sur l'hôte.

Ignorer l'architecture processeur. Une image contient des binaires compilés pour une architecture : amd64 (x86-64) ou arm64 (puces Apple, instances ARM des clouds). Lancer une image amd64 sur un hôte arm64 sans émulation produit cette erreur, déroutante la première fois :

exec /usr/local/bin/python: exec format error

La plupart des images officielles sont publiées pour plusieurs architectures, et Docker choisit automatiquement la bonne. Le problème apparaît surtout avec les images que vous construisez vous-même sur un Mac et poussez vers un serveur amd64. Le cours sur la construction d'images traite des images multi-architectures.

Croire que « Docker » est indispensable. Kubernetes ne prend plus en charge Docker Engine nativement depuis 2022, mais il exécute toujours vos images, parce qu'elles suivent le format OCI. Ce que vous apprenez ici sur les images, les couches et l'isolation reste valable avec Podman, containerd ou CRI-O.

Sécurité

Le partage du noyau a un prix : un conteneur isole moins qu'une machine virtuelle. Entre un processus conteneurisé et le noyau, il n'y a pas d'hyperviseur, seulement l'interface des appels système, qui compte plusieurs centaines d'entrées. Une faille du noyau exploitable depuis un conteneur compromet l'hôte et tous les autres conteneurs. Une faille du moteur aussi : la vulnérabilité CVE-2019-5736, révélée en février 2019, permettait à un processus malveillant dans un conteneur d'écraser le binaire runc de l'hôte, et donc d'obtenir root sur la machine au prochain docker exec.

Le guide NIST SP 800-190 consacré à la sécurité des conteneurs en tire une recommandation simple : ne pas mélanger sur un même hôte des conteneurs de niveaux de sensibilité différents. Concrètement :

  • Le processus sleep de tout à l'heure tournait en root, et c'était le root de l'hôte (uid 0), seulement restreint par les namespaces, les capabilities, seccomp et AppArmor. La leçon 12 montre comment l'éviter.
  • Une image est du logiciel tiers. La lancer, c'est exécuter le code de quelqu'un d'autre. Préférez les images officielles ou celles d'éditeurs vérifiés, et épinglez-les (leçon 7).
  • Quand l'isolation doit être forte (code non fiable, plusieurs clients sur une même plateforme), on ajoute une frontière supplémentaire : une VM par client, ou des moteurs comme gVisor (un noyau réimplémenté en espace utilisateur) ou Kata Containers (une micro-VM par conteneur).

Important

Un conteneur est une frontière d'organisation et de ressources très efficace, et une frontière de sécurité honnête mais imparfaite. Ne l'utilisez jamais comme unique barrière entre des données de clients différents.

En production

À l'échelle d'une organisation, l'intérêt des conteneurs dépasse largement le « ça marche sur ma machine » :

  • L'image devient l'unité de livraison. La chaîne CI construit une image, la teste, la signe et la pousse dans un registre ; la même image, identifiée par son empreinte, passe de la recette à la production. Les cours Livrer reposent entièrement sur cette idée.
  • L'immutabilité remplace la maintenance. On ne met plus à jour un serveur : on reconstruit l'image avec les correctifs et on remplace les conteneurs. La dérive de configuration disparaît par construction.
  • Les conteneurs appellent l'orchestration. Au-delà de quelques machines, il faut décider où placer chaque conteneur, le redémarrer quand il tombe, le relier aux autres et l'exposer. C'est le rôle de Kubernetes, qu'on retrouve chez Scaleway sous le nom de Kapsule.

Mais les conteneurs ne sont pas toujours le bon choix. Une base de données critique à forte charge d'entrées-sorties gagne souvent à tourner sur une VM ou un serveur dédié, avec un stockage maîtrisé. Un logiciel qui exige un noyau particulier, un pilote matériel ou un système Windows relève de la virtualisation. Et une application monolithique stable, déployée deux fois par an sur une seule machine, n'a pas toujours besoin de cette couche supplémentaire. Le bon réflexe est de se demander quel problème on résout, parmi les trois du début de cette leçon.

Exercices

1. Image ou conteneur ? Pour chaque affirmation, dites s'il s'agit d'une image ou d'un conteneur : (a) on peut en lancer dix exemplaires ; (b) il a un PID ; (c) on le télécharge depuis un registre ; (d) il possède une couche inscriptible ; (e) il est identifié par une étiquette comme 3.22.

Solution

(a) Une image : on lance dix conteneurs à partir d'une image. (b) Un conteneur : seul un conteneur en cours d'exécution a des processus. (c) Une image. (d) Un conteneur : la couche inscriptible est ajoutée à la création du conteneur. (e) Une image : l'étiquette désigne une version d'image.

2. Le noyau d'Alpine. Lancez docker run --rm alpine:3.22 uname -a puis uname -a sur l'hôte. Comparez. Que pouvez-vous en conclure sur le contenu de l'image Alpine ?

Solution

Les deux commandes affichent la même version de noyau (seul le nom d'hôte diffère, puisque le conteneur a son propre namespace UTS). L'image Alpine ne contient pas de noyau, seulement l'espace utilisateur d'Alpine (musl, BusyBox, apk). Le noyau est toujours celui de l'hôte.

3. Le processus dans l'arbre. Lancez docker run -d --name test alpine:3.22 sleep 300, puis retrouvez le processus depuis l'hôte avec ps -ef --forest | grep -B1 'sleep 300'. Quel est son parent ? Qui est le parent de ce parent ? Supprimez ensuite le conteneur.

Solution

Le parent de sleep 300 est containerd-shim-runc-v2. Le parent du shim est en général le processus 1 de l'hôte (systemd) : le shim s'est détaché de containerd pour que les conteneurs survivent à un redémarrage de containerd. Nettoyez avec docker rm -f test.

4. Choisir son isolation. Lyneko doit héberger, pour trois collectivités différentes, la même application de gestion de dossiers, qui traite des données personnelles. Proposez une architecture et justifiez où placer les frontières entre clients.

Solution

Une approche raisonnable : la même image pour les trois clients (une seule chaîne de livraison), mais des conteneurs séparés par client, et surtout des nœuds ou des VM distincts par client (ou un cluster par client) pour que la frontière entre données de collectivités différentes ne repose pas uniquement sur l'isolation du noyau. Les bases de données sont séparées par client, avec des identifiants distincts. Selon la sensibilité, on peut aussi s'appuyer sur une offre qualifiée SecNumCloud. L'important est de justifier : le conteneur organise et limite, la VM (ou le cluster) isole.

Récapitulatif

  • Les conteneurs résolvent trois problèmes : les conflits de dépendances, la dérive de configuration et le coût des machines virtuelles.
  • Une image est un modèle figé et transportable ; un conteneur est une instance en cours d'exécution, comme un processus l'est pour un programme.
  • Un conteneur est un processus Linux ordinaire isolé par des namespaces, limité par des cgroups et restreint dans ses privilèges. Il partage le noyau de l'hôte.
  • Docker n'a pas inventé l'isolation mais l'emballage ; le format d'image est désormais un standard OCI, utilisé par Podman, containerd et Kubernetes.
  • Un conteneur isole moins qu'une VM : on ne mélange pas sur un même hôte des charges de sensibilités différentes.

Pour aller plus loin

  • L'article fondateur des jails de FreeBSD (Kamp et Watson, 2000) : court, lisible, et étonnamment actuel.
  • La série d'articles de Michael Kerrisk sur les namespaces publiée par LWN.net en 2013, toujours la meilleure introduction détaillée.
  • Le guide NIST SP 800-190 : une lecture de référence pour les décideurs et les responsables sécurité.
  • Leçon suivante : nous allons construire un conteneur à la main, sans Docker, avec unshare et quelques commandes, pour voir que tout ce qui précède n'est pas une figure de style.

Sources