Initiation à Docker — Conteneurisation d'un serveur web
Technicien : Dufosse
Module : BTS SIO SISR — VIRT-05 « Initiation à Docker » et « Persistance et réseau des conteneurs »
Environnement : VM Ubuntu 26.04, administrée en SSH
Docker est un moteur de conteneurisation : il isole une application avec ses librairies et dépendances dans un environnement autonome. Contrairement à la virtualisation classique, où c'est le matériel qui est partagé, les conteneurs partagent le noyau de l'OS hôte, ce qui les rend bien plus légers et rapides à lancer.
Dans ce TP, j'ai installé Docker sur une VM Ubuntu, construit ma propre image de serveur
web nginx à partir d'un Dockerfile, publié le service sur le réseau, personnalisé
son contenu (directives COPY et ADD), ouvert un shell interactif dans un
conteneur et rendu ses journaux persistants grâce à un volume.
Étape A — Installation et construction de l'image
J'ai installé le moteur Docker depuis les dépôts Ubuntu (paquet docker.io,
version 29.1.3), dans un répertoire de travail dédié au TP.
mkdir ~/tp_docker && cd ~/tp_docker
apt update
apt install -y docker.io
J'ai écrit un Dockerfile qui part d'une image Ubuntu, installe nginx, expose le
port 80 et lance nginx au premier plan (daemon off;) pour que le conteneur reste
actif. L'image est construite sous le nom mon_serveur_nginx.
FROM ubuntu:26.04
RUN apt update \
&& apt install -yf \
nginx
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
docker build -t mon_serveur_nginx .
Au lancement, Docker télécharge l'image de base ubuntu:26.04 depuis Docker Hub, couche par couche.
Les avertissements debconf affichés pendant le build ne sont pas des erreurs :
aucun terminal n'est disponible pendant la construction, l'installation passe donc en
mode non interactif. Les services ne démarrent pas non plus à ce stade, c'est l'instruction
CMD qui lance nginx au démarrage du conteneur.
docker image ls montre deux images : mon_serveur_nginx (236 Mo) et
ubuntu:26.04 (160 Mo). L'image Ubuntu est présente car elle sert de base
(FROM) : Docker la télécharge depuis Docker Hub puis empile les couches du
Dockerfile par-dessus. La différence de taille correspond à la couche nginx.
docker run --name instance_nginx --detach --publish 8000:80 mon_serveur_nginx
Le conteneur tourne en arrière-plan et le port 8000 de la VM est redirigé vers le port 80
du conteneur. On voit apparaître le pont docker0 et l'interface virtuelle
veth du conteneur. Le serveur est ensuite géré avec docker ps
(liste des conteneurs actifs) et docker stop.
docker ps confirme le conteneur instance_nginx actif avec le port 8000 publié, puis docker stop l'arrête.
Étape B — Personnalisation du contenu
J'ai ajouté un fichier index.html à l'image grâce à la directive
COPY, placée juste avant EXPOSE, puis reconstruit l'image et
relancé le conteneur (après suppression de l'ancien, dont le nom était encore réservé).
COPY ./index.html /var/www/html/index.html
Le navigateur affiche désormais ma page à la place de la page par défaut de nginx.
J'ai remplacé COPY par ADD, qui peut récupérer un fichier depuis une
URL au moment du build. Le conteneur sert alors la page « World Wide Web » du CERN, le tout
premier site web de l'histoire.
ADD --chown=www-data:www-data http://info.cern.ch/hypertext/WWW/TheProject.html /var/www/html/index.html
L'option --chown donne le fichier à l'utilisateur www-data, sous
lequel tourne nginx. Sans elle, le fichier téléchargé appartiendrait à root avec des
droits restreints et nginx renverrait une erreur 403.
| Directive | Fichiers locaux | URL distante | Extraction tar auto |
|---|---|---|---|
COPY | Oui | Non | Non |
ADD | Oui | Oui | Oui |
Bonne pratique : utiliser COPY par défaut, plus prévisible, et réserver ADD aux cas qui en ont besoin.
Étape C — Shell interactif et persistance
J'ai ouvert un shell directement dans un conteneur. L'option -i garde l'entrée
standard ouverte et -t alloue un pseudo-terminal : ensemble, elles donnent un
shell interactif. Le /bin/bash remplace le CMD, nginx n'est donc pas
lancé dans ce cas.
docker run -i -t mon_serveur_nginx /bin/bash
La capture illustre l'isolation : depuis l'intérieur du conteneur, la commande
docker n'existe pas. Le conteneur a son propre système de fichiers et ne voit pas
les outils installés sur l'hôte.
J'ai créé un volume nommé nginx_log et l'ai monté sur /var/log/ du
conteneur pour rendre les journaux persistants.
docker volume create nginx_log
docker run -d -p 8000:80 -v nginx_log:/var/log/ mon_serveur_nginx
Sur l'hôte, le volume se trouve dans /var/lib/docker/volumes/nginx_log/_data/.
Comme il était vide, Docker l'a rempli au premier montage avec le contenu existant de
/var/log du conteneur.
| Fichier | Rôle |
|---|---|
nginx/access.log | Chaque requête HTTP : IP client, date, méthode et page, code de réponse, navigateur |
nginx/error.log | Erreurs de nginx (vide : aucune erreur) |
apt/ | Historique des installations apt faites pendant le build |
dpkg.log | Détail des paquets installés |
alternatives.log | Liens système créés pendant l'installation |
Les requêtes de mon navigateur apparaissent dans access.log avec un code
304 Not Modified (page déjà en cache). Les logs restent sur l'hôte même si le
conteneur est supprimé, et se consultent sans entrer dans le conteneur.
Étape D — Persistance avec un bind mount
Un conteneur est stateless : s'il est supprimé, ses données disparaissent avec lui. Après avoir arrêté les instances précédentes, j'ai monté un dossier de la VM directement dans le conteneur (bind mount), à l'emplacement servi par nginx.
docker rm -f $(docker ps -q)
mkdir ~/tp_docker/data
docker run -d -p 8000:80 -v ~/tp_docker/data/:/var/www/html/fichiers mon_serveur_nginx
J'ai créé document.html sur la VM uniquement. Il apparaît aussitôt dans le
conteneur, puis nginx le sert sans reconstruire l'image.
| Type | Emplacement sur l'hôte | Usage typique |
|---|---|---|
| Volume nommé | Géré par Docker (/var/lib/docker/volumes) | Données applicatives, logs |
| Bind mount | Dossier choisi (~/tp_docker/data) | Contenu modifié depuis l'hôte, fichiers de config |
Étape E — Réseau entre conteneurs
J'ai créé un réseau Docker dédié et y ai connecté le serveur nginx sous le nom
serveur. Le réseau utilise le driver bridge avec le sous-réseau
172.18.0.0/16, distinct du réseau par défaut en 172.17.0.0/16.
docker network create tp_ct_net
docker run -d -p 8000:80 -v ~/tp_docker/data/:/var/www/html/fichiers \
--network tp_ct_net --name serveur mon_serveur_nginx
docker network inspect tp_ct_net
J'ai lancé un second conteneur Alpine sur le même réseau, puis pingé le serveur par son nom.
docker run --network tp_ct_net -it alpine:latest /bin/sh
ping -c 4 serveur
Le nom serveur est résolu en 172.18.0.2 grâce au DNS interne que
Docker fournit sur les réseaux créés par l'utilisateur (absent du réseau par défaut).
Résultat : 4 paquets reçus sur 4, 0 % de perte, environ 0,1 ms de latence.
Bilan
Ce TP m'a permis de comprendre le cycle de vie complet d'un conteneur : écriture d'un Dockerfile, construction d'une image en couches, exécution, publication de ports, mise à jour du contenu, accès interactif, persistance des données (volumes et bind mounts) et mise en réseau de plusieurs conteneurs. Ce sont les mêmes briques que j'utilise sur mon serveur personnel, où plusieurs services tournent en conteneurs.