Durant mon cursus d'apprentissage, je n'avais pas appris Docker. Je connaissais vaguement son principe, mais je ne comprenais pas vraiment son intérêt par rapport à une machine virtuelle. Je l'ai laissé trop longtemps de côté.
Je m'y suis mis réellement il y a maintenant à peu près six mois. J'ai pris des conteneurs sur la tête, ça, c'est certain ! Mais je dirais qu'en fait, je ne peux tout simplement plus m'en passer.
Que ce soit pour passer un projet à un copain, déployer une application ou éviter les erreurs liées à un système en particulier, c’est vraiment un must have dans le dev.
Bien évidemment, cela demande un peu de temps d'apprentissage, mais ça en vaut la peine.
Je ne prétends pas être un "Docker Master" ni que cet article te permettra de sauter la phase d'apprentissage, mais il t'aidera au moins à en saisir tout l’intérêt.
La Théorie des conteneurs
Ça paraît bête, mais si on n'en voit pas l’intérêt (comme moi avant), on n'aura pas envie de l'apprendre.
Docker, pour ceux qui ne connaissent pas, c'est un système de virtualisation par image, bien plus léger qu'un hyperviseur du genre Proxmox ou VMware.
En fait, on peut installer Docker sur quasiment n'importe quel système, et il devient une interface de lancement d’applications. Cela nous permet de nous affranchir de la couche d'infrastructure de notre système.
Par exemple, si je travaille sur Debian, que je développe hors Docker, puis que j'installe mon projet sur une Fedora, je vais devoir refaire toute la configuration pour le faire fonctionner.
En revanche, si je commence avec Docker sur Debian et que je lance mon application sur le Docker de ma Fedora, je n'aurai aucune différence de configuration, car Docker fait office de couche d’abstraction.
Un autre avantage, c'est sa capacité à mettre à jour les versions des systèmes qu'on utilise en ne changeant que quelques lignes.
Avec Docker, on crée des Dockerfile, qui sont des fichiers d'instructions servant à générer des images pour exécuter nos applications. C'est comme si l'on créait un système 100 % dédié à notre application.
Cette image, on peut ensuite la charger et la lancer dans un conteneur (l'équivalent d’une petite machine virtuelle) qui tournera sur Docker avec la configuration que l'on aura choisis au préalable via des fichiers de configuration.
On peut aussi créer des réseaux internes pour permettre à plusieurs conteneurs de communiquer entre eux.
Finalement, les intérêts sont multiples : le CI/CD, l'abstraction de l'infrastructure, la facilité de mise à jour d'un service… Les avantages sont nombreux !
Pour suivre ce tutoriel tu devras bien évidement avoir Docker sur ta machine 😅 Pour l’installation c'est par ici !
Notre première image
On peut soit utiliser une image disponible sur Docker Hub, qui est la référence pour toutes les images officielles (attention à toujours bien vérifier la source), soit créer la nôtre.
L'intérêt ici est justement de créer notre propre image. On verra plus tard comment récupérer une image depuis le Hub, car cela peut parfois nous faire gagner du temps.
On commence donc par créer un fichier Dockerfile (sans extension).

Première étape : le système
Puisque notre image va servir à créer un environnement isolé (comme une machine virtuelle), la première ligne de notre Dockerfile doit définir quel système on va utiliser comme base.
C'est un peu comme pour un PC : pour qu'une application fonctionne, il faut lui dire sur quel OS elle va tourner.
Pour ce tutoriel, on va utiliser une image ultra légère d'un Linux intégrant PHP et Apache, qui contient déjà tous les packages liés à PHP :
FROM php:8.3-apache
C'est donc la première brique de notre image.
Dans ce FROM, il y a en réalité d'autres instructions, déjà définies par des contributeurs open source, qui nous permettent de l'utiliser aussi simplement.
C'est une sorte de raccourci : sinon, on devrait à chaque fois tout recréer ligne par ligne pour installer et configurer le système de base.
Ensuite, on va lui indiquer dans quel répertoire se trouvera notre application à l’intérieur du conteneur.
FROM php:8.3-apache
WORKDIR /var/www/html
Quand on lancera notre conteneur plus tard, on sera directement positionné dans ce dossier.
Docker utilisera alors ce chemin comme référence relative pour toutes les instructions suivantes.
On va maintenant lui indiquer ce que l'on souhaite exécuter comme projet.
Pour le tutoriel, ce sera simplement un fichier PHP qui affiche un texte avec un echo.
On crée donc un fichier index.php contenant juste un echo, puis on l'ajoute dans notre image.
L'instruction COPY indique à Docker de copier l'ensemble du répertoire de notre machine hôte (celui où se trouve le Dockerfile) dans le répertoire "." du conteneur — ici, sous-entendu /var/www/html/mon_premier_docker.
FROM php:8.3-apache
WORKDIR /var/www/html
COPY . .
Parfait, on a maintenant notre fichier, c’est top !
Il ne reste plus qu'à le transformer en image Docker grâce à une simple commande.
docker build -t ma-premiere-image-docker:latest .

On voit bien les steps (étapes) qui se construisent lors de la création de notre image.
Chaque ligne d'instruction de notre Dockerfile correspond à un Step, qu'on appelle aussi une couche (Layer).
Une fois la construction terminée, on peut vérifier la présence de notre image dans Docker avec la commande suivante : docker image ls.

Notre image est maintenant prête : c'est une sorte de template.
Il faut à présent la transformer en conteneur à l'aide de la commande suivante :
docker run -d --name mon-premier-container-docker -p 80:80 ma-premiere-image-docker:latest
Concrètement, qu'est-ce qu’on fait ici ?
-
On donne un nom à notre conteneur grâce à l'option
--name. -
On mappe le port 80 de notre machine vers le port 80 du conteneur, afin de pouvoir y accéder depuis le navigateur.
-
On associe l'image que nous avons créée au conteneur.
-
Le
-dpermet de détacher le processus de la console pour libérer le terminal et continuer à exécuter d'autres commandes ensuite.
À présent, on peut ouvrir son navigateur et vérifier que tout fonctionne en se rendant sur : http://localhost:80

On a bien notre index.php qui est servi par notre conteneur !
Finalement, on vient tout simplement de créer un serveur Apache qui sert des fichiers PHP.
La configuration est volontairement simple ici, mais c'était suffisant pour l’exemple.
On pourra, à notre convenance, y ajouter d’autres services comme phpMyAdmin et une base de données.
Et justement… on va voir ça tout de suite !
Les docker compose
C'est un autre fichier que l'on associe souvent aux Dockerfile, mais qui, en réalité, est totalement indépendant.
On peut tout à fait avoir un projet fonctionnel avec zéro Dockerfile dans le dossier, à condition que les images utilisées soient déjà disponibles sur le web (par exemple sur Docker Hub ou un repository privé).
Les Docker Compose nous permettent d'ajouter facilement des réseaux et des volumes afin de rendre les données persistantes.
Car oui, si on éteint un conteneur, toutes les informations présentes à l'intérieur de la "VM" disparaissent avec lui.
D'où l'intérêt de monter des volumes pour sauvegarder certaines données, comme celles d’une base de données, par exemple.
On va donc se créer un nouveau fichier nommé docker-compose.yml :
services:
Chaque première indentation sera le nom de nos services :
services:
db:
phpmyadmin:
Ici on aura donc 2 services lors de la création de nos images : db et phpmyadmin
Pour chaque service, on va lui indiquer l'image qu’il doit utiliser :
services:
db:
image: mariadb:10.11
phpmyadmin:
image: phpmyadmin
En cas de plantage ou de soucis avec notre container, on a également besoin qu'il se restart automatiquement, on lui ajoute donc une propriété restart=always . Pour ceux qui sont familier avec systemd c'est un peu le même genre.
services:
db:
image: mariadb:10.11
restart: always
phpmyadmin:
image: phpmyadmin
restart: always
Pour notre DB on prend le port par défaut de mariadb qui est 3306 donc on ne le spécifiera pas. Par contre pour phpmyadmin, on va changer son port par défaut et mettre 8080 :
services:
db:
image: mariadb:10.11
restart: always
phpmyadmin:
image: phpmyadmin
restart: always
ports:
- 8080:80
On va maintenant indiquer les variables d'environnements pour paramétrer notre DB, notamment le nom de notre DB, le password etc …
services:
db:
image: mariadb:10.11
restart: always
environment:
MYSQL_ROOT_PASSWORD: monSupermotdepassequitue!
MYSQL_DATABASE: ma_premiere_db_docker
phpmyadmin:
image: phpmyadmin
restart: always
ports:
- 8080:80
Par défaut un container prendra le nom de son service, mais on peut aussi lui changer :
services:
db:
image: mariadb:10.11
restart: always
container_name: ma-premiere-db-docker
environment:
MYSQL_ROOT_PASSWORD: monSupermotdepassequitue!
MYSQL_DATABASE: ma_premiere_db_docker
phpmyadmin:
image: phpmyadmin
restart: always
ports:
- 8080:80
Voila ! On a nos 2 services, mais on peut aussi prendre le dockerfile que l'on vient de créer pour en faire un troisième :
services:
db:
image: mariadb:10.11
restart: always
container_name: ma-premiere-db-docker
environment:
MYSQL_ROOT_PASSWORD: monSupermotdepassequitue!
MYSQL_DATABASE: ma_premiere_db_docker
phpmyadmin:
image: phpmyadmin
restart: always
ports:
- 8080:80
php:
build: .
restart: always
container_name: mon-premier-container-docker
ports:
- 80:80
On indique ainsi au docker-compose d'utiliser le build créé par le dockerfile comme base de ce service.
J'ai conscience que ça fait pas mal de commandes, pas mal de fichiers et d'instructions mais à force de les utiliser, comme GIT, ça devient naturel !
On peut ainsi lancer la commande docker compose -d up pour indiquer que l'on veut monter nos container directement après la création des images :

On voit bien toutes les layers qui se créent et qui se lancent.
A présent, on peut aller sur localhost:8080 et utiliser l'admin root et le password de notre docker-compose :

et notre PHPMyAdmin voit bien notre base de données :

Mais alors comment ça se fait ? c'est censé être des containers séparés logiquement PHPMyAdmin ne devrait même pas voir ma_premiere_db_docker.
Et bien en fait tout ce qui se trouve dans le docker-compose (sauf indication contraire) se trouvera dans le même réseau créé par défaut.
On peut voir la liste des réseaux avec cette commande : docker network list:

Il prend le nom du dossier dans lequel se trouve le docker-compose.yml. On pourra le changer par la suite. Pour voir quels containers se trouvent dans notre réseau on fait
docker network inspect docker_intro_default.
ça nous permettra plus tard de récupérer les adresses IP pour communiquer avec nos containers si besoin et surtout de savoir en cas de debug si mon container est bien dans le bon réseau 😅.
Ici on voit bien que PHPMyAdmin est dans le réseau de la DB, ils peuvent donc communiquer ensemble.
On va insérer quelques data à présent via PMA, on va donc dans notre db ma_premiere_db_docker puis on insère :

Voyons voir ce que ça donne si on simule un crash de serveur en éteignant notre container.
On exécute la commande suivante docker compose down .

On relance pour retourner sur notre PMA docker compose up -d .
Et là … c’est le drame !
Aucune table, alors qu’on vient de les créer … Pourquoi ???
Tout simplement parce que notre container ne crée rien de persistant si on ne lui indique pas. Tout est volatile avec les containers, sauf si on lui dit le contraire.
Pour ça on utilise la propriété "volumes" dans notre docker-compose :
db:
image: mariadb:10.11
restart: always
container_name: ma-premiere-db-docker
environment:
MYSQL_ROOT_PASSWORD: monSupermotdepassequitue!
MYSQL_DATABASE: ma_premiere_db_docker
volumes:
- ./db:/var/lib/mysql
On dit à notre container d'utiliser le dossier db comme volume persistant à notre base de données mariadb.
Si le dossier db n’existe pas côté local, il sera créé si le user docker à les droits sinon il y aura une erreur (passer en sudo si besoin).
On recrée les containers directement via docker compose up -d --build pour reforcer le build des images des services.
On retourne sur notre PMA et on récrée notre première table comme plus haut.
On stop notre container : docker compose down puis directement docker compose up et là … Tadaaaaaam ! La base de données a persistée ! On peut donc y sauver de la data sans crainte de perdre les data.
On peut aussi faire de même pour notre petit container PHP , on crée un sous dossier ./app et on y mets notre index.php.puis on vient mappé notre volume :
php:
build: .
restart: always
container_name: mon-premier-container-docker
volumes:
- ./app:/var/www/html/
On modifie le Dockerfile pour faire correspondre ce changement de dossier :
COPY . . ⇒ COPY ./app .
Mais pourquoi on fait ça ? Etant donné que l'on fige les informations de l'image vers le container, sans volume partagé on devrait à chaque modification de notre code, rebuild notre image. Hors ici, avec le volume partager, on peut directement modifier notre code PHP sans rebuild !
Si tu veux voir les logs de ton app, tu peux aussi créer un volume avec en source les logs de apache :
volumes:
- ./app:/var/www/html/
- ./logs:/var/log/apache2/
rebuild et lance le container directement avec docker compose up -d --build et dans ton répertoire :
Tu peux aussi aller directement dans ton container pour regarder des fichiers au besoin à l'aide la comande :
docker exec -ti mon-premier-container-docker /bin/bash
Pourquoi ponctuellement ? Souviens toi, à chaque fois que tu éteins ton container, quand tu le rédémarera, il démarrera comme au tout début, juste au niveau du build. Donc si tu veux un paquet en plus, tu peux l’ajouter dans ton Dockerfile :
FROM php:8.3-apache
RUN apt-get update && apt-get install -y nano
WORKDIR /var/www/html
COPY ./app .
Tu sais maintenant démarrer docker avec des images créés toi même, mettre tes containers dans des réseaux et garder des données persistantes.
Par contre, tu vas vite voir qu’avec docker, c'est très facile d’avoir des tas d'images et des tas de containers inutilisées 😅. Pour le bien de ton stockage et de tes ressources, tu devras éteindre et faire le tri dans les containers.
Pour lister tes images ou tes containers : docker image ls et docker container ls
Tu peux aussi voir les containers qui sont actifs avec docker ps (un peu comme ps aux pour les unix). C’est utile pour voir aussi si tes containers sont bien allumés.

Chaque ID correspond à une référence unique du conteneur et tu peux donc en arrêter un manuellement avec docker stop f1f (Seuls les trois premiers caractères de l'ID suffisent en général).
Pense aussi à supprimer tes images avec docker image rmi <iddetonimage>.
Et si tu veux faire un nettoyage global, tu peux exécuter docker image prune. Cette commande supprimera toutes les images qui ne sont associées à aucun conteneur, ce qui te permettra d'économiser de l’espace disque.
J'aurais pu aussi te faire une liste du style "Les 20 commandes Docker les plus utilisées", mais ce n’est pas vraiment utile pour le moment. Inutile de t'assommer avec des commandes que tu oublierais tant que tu ne maîtrises pas déjà celles que l’on vient de voir.
Je vais par contre te les récapituler ici :
docker build -t <nomdetonimage>:<tag/version> <dossierDeTonDockerfile>ça te permet de créer l'image via ton Dockerfile.docker run -d --name <nomdetoncontainer> -p <port:port> <nomdetonimage>:<tag/version>(Utile aussi pour lancer une image depuis le dockerHub)docker compose -d up(lancer depuis le dossier où est le docker-compose.yml) Le-dpermet uniquement de se détacher de la tâche dans la console (pour ne pas à ouvrir un second terminal et le lancer en tâche de fond) Si on rajoute--buildon reconstruit via les Dockerfiles ou les images mises à jour.docker composepermet de build les images (ou les récupérer via le cloud) et de lancer les containers associés avecup.docker compose down(éteint l'ensemble des containers du docker-compose.yml)docker network list(Affiche la liste de tous tes networks)docker network inspect <nondetonréseau>(Affiche plein d'infos sur le réseau et les containers associés)docker exec -ti <nomducontainer/idducontainer> /bin/bash(-tipermet d’accéder à la commande executée avec/bin/bashon affiche donc un terminal dans le container).
Avec ça, tu peux faire tes armes Docker. Tu pourras aussi étoffer tes docker-compose.yml au fil du temps ! Et surtout, ça te permettra de te détacher de ton système de développement initial et du "ça marche sur mon poste" !
elle fonctionne dans beaucoup trop de situation 😅)
Je tiens aussi à remercier Cédric qui se reconnaîtra pour m'avoir beaucoup aidé dans mon apprentissage Docker. Avant de découvrir cette manière de virtualiser j'étais un fervent défenseur des VMs que l’on créé via Proxmox. Je ne retournerai pas en arrière.
Et petite dédicace à mon frère qui bosse au port du Havre entouré de conteneurs toute la journée, chaque fois que je fais une commande docker, j’ai une petite pensée pour lui ! ❤️
A présent je me rends compte que finalement on n'est pas obligé de sortir l'artillerie lourde pour abattre un moustique ! Et l'avantage avec Docker, c'est que ça va te faire progresser en réseau et en commandes Linux ! Alors tu attends quoi pour lancer ton premier conteneur ?

