Cours introductif — préalable à la série de TP sur Docker
Une VM recrée un système complet (kernel + OS + applications) à l'intérieur d'un système hôte, via un hyperviseur. Chaque VM dispose de son propre noyau et croit s'exécuter sur une machine physique dédiée.
On distingue deux familles d'hyperviseurs :
| Type | Description | Exemples |
|---|---|---|
| Type 1 (bare-metal) | L'hyperviseur s'exécute directement sur le matériel, sans OS hôte. | ESXi, Proxmox VE (KVM), Hyper-V |
| Type 2 (hosted) | L'hyperviseur est une application qui tourne au-dessus d'un OS hôte. | VirtualBox, VMware Workstation |
C'est précisément ce dernier constat — des ressources réservées mais sous-utilisées — qui a motivé l'apparition d'une virtualisation plus légère : les conteneurs.
Un conteneur est une enveloppe qui empaquette une application avec toutes ses dépendances (binaires, runtime, librairies, outils, fichiers de configuration, scripts…) en un ensemble cohérent, prêt à être déployé.
Différence fondamentale avec une VM : un conteneur n'embarque pas de noyau ni d'OS. Il s'appuie directement sur le noyau du système hôte.
Les conteneurs sont nés sous Linux (Chroot, puis OpenVZ, LXC), en s'appuyant sur deux mécanismes du noyau :
Un conteneur Linux est un ensemble de processus isolés du reste du système.
Contrairement à la VM, il ne virtualise pas les ressources : il ne fait qu'isoler des processus. Les ressources (CPU, RAM, stockage) restent partagées avec l'hôte et les autres conteneurs.
Puisque les conteneurs partagent le noyau de l'hôte, ils sont liés à ce noyau :
➡️ On ne peut pas exécuter un conteneur Windows ou BSD sur un hôte Linux (et inversement). Le conteneur n'apporte qu'une isolation des processus, pas une indépendance vis-à-vis du système.
Un conteneur ne réserve pas les ressources : il les consomme à la demande. On peut fixer une limite de 16 Go de RAM ; si le conteneur n'en utilise que 2 Go, le reste reste disponible pour l'hôte et les autres conteneurs.
À nuancer : les hyperviseurs modernes savent eux aussi sur-allouer dynamiquement la RAM (ballooning, thin provisioning). L'avantage des conteneurs ici est de degré, pas de nature.
Pas de noyau ni d'OS à booter : un conteneur démarre en fractions de seconde, là où une VM met plusieurs dizaines de secondes. On peut donc en créer et en détruire fréquemment (montée en charge, CI/CD, tests éphémères).
La scalabilité automatique repose sur des orchestrateurs (Kubernetes, Docker Swarm, Nomad), qui démarrent ou arrêtent des conteneurs selon la charge.
Le fonctionnement des conteneurs et des VM est proche, mais l'un ne remplace pas l'autre. D'ailleurs, un conteneur a toujours besoin d'une machine pour s'exécuter — physique ou virtuelle. En pratique, on déploie très souvent des conteneurs à l'intérieur de VM.
| Machine virtuelle | Conteneur | |
|---|---|---|
| Contient | Noyau + OS + application | Application + dépendances |
| S'appuie sur | Un hyperviseur | Le noyau de l'hôte |
| Isolation | Très forte | Plus légère (noyau partagé) |
| Démarrage | Lent (boot d'un OS) | Quasi instantané |
| Empreinte | Lourde (Go) | Légère (Mo) |
| OS invité | Libre (Linux, Windows, BSD…) | Imposé par le noyau de l'hôte |
➡️ À retenir : on choisit la VM pour une isolation forte et un OS différent ; le conteneur pour la légèreté, la rapidité et la densité. Les deux se combinent.
Précision historique : à ses débuts (2013), Docker s'appuyait sur LXC. Dès 2014, il l'a remplacé par sa propre brique (
libcontainer, devenuerunc). Aujourd'hui, Docker n'utilise plus LXC : il pilotecontainerd+runc, qui exploitent directement les namespaces et cgroups du noyau.
Docker introduit une rupture par rapport aux conteneurs « système » (LXC, OpenVZ) :
➡️ Un conteneur ne doit faire tourner qu'un seul service.
Exemple — une stack LEMP (Linux, Nginx, MySQL, PHP) :
Cette philosophie favorise l'architecture microservices : structurer une application comme un ensemble de services faiblement couplés, donc déployables, scalables et maintenables indépendamment.
Docker répond à une problématique récurrente du développement :
Docker ne convient pas à tous les usages. Il est moins adapté lorsqu'il faut faire persister de grands volumes de données avec une forte exigence de continuité de service (cf. notion d'état, §6).
| Édition | Plateforme | Licence |
|---|---|---|
| Docker CE (Community Edition / Engine) | Linux | Gratuit, open source |
| Docker Desktop | Windows, macOS, Linux | Gratuit pour usage personnel / petites structures, payant au-delà |
| Docker Business / Enterprise | Multi-plateforme | Licence commerciale (gestion de parc, sécurité avancée) |
Sur un serveur de production, on privilégie Docker CE sous Linux : Docker est fondamentalement bâti sur le noyau Linux. (Sur Windows/macOS, Docker Desktop exécute en réalité les conteneurs Linux dans une VM Linux masquée.)
Au lancement, Docker superpose deux couches :
Toutes les modifications faites pendant la vie du conteneur (fichiers créés, paquets installés…) vont dans cette couche d'écriture.
L'image est immuable : une fois construite, elle ne change pas. Deux conteneurs lancés depuis la même image partent exactement du même état initial.
➡️ Conséquence : la couche d'écriture d'un conteneur est éphémère. Quand le conteneur est supprimé, tout ce qui a été écrit dedans est perdu. On ne modifie pas un conteneur « en place » sur la durée : on reconstruit une nouvelle image et on relance un conteneur neuf.
| Stateless (sans état) | Stateful (avec état) | |
|---|---|---|
| Données | Aucune donnée à conserver entre deux exécutions | Doit conserver des données dans le temps |
| Exemples | Serveur web statique, API de calcul, frontend | Base de données, file de messages, stockage de fichiers |
| Avec Docker | Cas idéal : le conteneur peut être détruit/recréé librement | Nécessite un volume pour externaliser les données |
Puisque la couche d'écriture est éphémère, les données qui doivent survivre au conteneur sont stockées hors du conteneur, dans un volume.
➡️ Exemple typique : pour exécuter une base de données dans un conteneur, on monte un volume sur son répertoire de données (ex. /var/lib/mysql). Ainsi, on peut détruire et recréer le conteneur sans perdre la base.
Bonne pratique : garder les conteneurs aussi stateless que possible, et isoler explicitement l'état dans des volumes. C'est ce qui réconcilie l'immutabilité de l'image avec le besoin de persistance.
Docker repose sur une architecture client / serveur.
| Composant | Rôle |
|---|---|
| Docker Host | La machine (physique ou VM) où s'exécutent le daemon et les conteneurs. Souvent appelée simplement « l'hôte ». |
Docker Daemon (dockerd) |
Le serveur. Construit et stocke les images ; crée, démarre et supervise les conteneurs. C'est un service Linux géré par systemd. |
Docker Client (docker) |
L'outil en ligne de commande. Dialogue avec le daemon via une API REST pour gérer conteneurs, images et registries. |
| Docker Engine | L'ensemble Client + Daemon. |
| Docker Registry | Stocke et distribue les images. Le registry public par défaut est le Docker Hub (des milliers d'images officielles et communautaires). On peut aussi héberger un registry privé. |
Dockerfile, le daemon construit une image (en couches, immuable).run, exec, logs, stop, rm…) via l'API.La sécurité des conteneurs est cruciale, en particulier sur des infrastructures critiques. Docker simplifie le déploiement, mais introduit des risques spécifiques qu'il faut comprendre et réduire.
🔗 Pour approfondir : it-connect.fr — Docker : top 5 des risques cybersécurité
Tous les conteneurs partagent le noyau de l'hôte. Une vulnérabilité dans ce noyau peut permettre à un attaquant de s'échapper d'un conteneur pour atteindre l'hôte ou les autres conteneurs.
Exemple : CVE-2019-5736 (faille dans
runc) permettait de s'évader d'un conteneur.
Mitigations : maintenir le noyau et runc/containerd à jour ; activer les profils seccomp, AppArmor/SELinux ; envisager une isolation renforcée (gVisor, Kata Containers) pour les charges sensibles.
Une image peut embarquer des vulnérabilités ou du code malveillant. Le contenu d'une image est plus opaque que l'installation d'un OS complet, et une image issue d'un dépôt non vérifié peut contenir une backdoor ou des paquets obsolètes aux failles connues.
Mitigations : privilégier les images officielles et minimales ; scanner les images (Trivy, Docker Scout, Grype) ; vérifier les signatures ; figer les versions (pas de tag latest en production).
Exécuter un conteneur avec des privilèges élevés (root, --privileged) expose l'hôte : si une faille est exploitée, l'attaquant peut rebondir vers l'hôte.
Mitigations : exécuter en utilisateur non-root ; éviter --privileged ; supprimer les capabilities inutiles (--cap-drop) ; activer le user namespace remapping.
Une configuration réseau mal maîtrisée (pare-feu, segmentation) peut permettre à un attaquant d'atteindre, depuis un conteneur compromis, d'autres services du réseau interne.
Mitigations : segmenter avec des réseaux Docker dédiés ; n'exposer (-p) que les ports nécessaires ; appliquer un pare-feu hôte cohérent ; éviter le mode --network host sauf besoin avéré.
🚀 Place à la pratique !