Table des matières

Mise en route

Avant toute chose, une convention:

Sous une question, la réponse sera masquée de la sorte

Le contenu masqué

Récupérer l’image

L’ensemble des outils dont vous aurez besoin a été compilé dans une machine virtuelle que vous pourrez lancer avec VirtualBox. Mais avant cela vous allez devoir la récupérer en utilisant ce lien:

https://u.pcloud.link/publink/show?code=XZE9YUXZx69eQYLHi04Ser63hxMiY0o87Aiy

Une fois récupérée, ouvrez/décompressez l’archive à l’endroit de votre choix.

Lancement de la VM

Une fois la VM lancée, vous devriez avoir:

À partir de maintenant toutes les manipulations se feront dans la VM.

Lancez VSCode, ouvrez le dossier app. Si VSCode le demande, faites confiance au contenu du dossier:

drawing

Step 1 : lancement de l’application à la main

Une fois le dossier app ouvert dans VSCode, ouvrez un terminal en faisant Terminal/New Terminal.

Nous allons compiler l’application et la lancer. Dans le terminal, lancez les commandes suivantes:

cd app
yarn install --production
node src/index.js

Normalement vous devriez obtenir quelque chose comme ceci:

ubuntu@ubuntu-VirtualBox:~/Desktop/app$ node src/index.js
Using sqlite database at /etc/todos/todo.db
Listening on port 3000

Super! C’était facile!…

Non, pas vraiment. Pour en arriver là, il m’a fallu, en arrière plan:

J’ai donc perdu du temps à configurer mon environnement. Et encore, il s’agit d’une toute petite application!

Step 2: contexte

Ce que propose Docker, c’est de packager l’application dans un conteneur. De loin un conteneur ressemble un peu à une Machine Virtuelle, mais il existe entre les deux une différence fondamentale.

Une machine virtuelle est, comme son nom l’indique, une machine complète qui est émulée sur une machine hôte. Comme ici par exemple, je vous ai livré un Ubuntu tout prêt. Dans ce cas, l’hôte et la VM on chacun leur propre noyau, complètement indépendant l’un de l’autre.

Un conteneur, quant à lui, partage son noyau avec celui de l’hôte et ne contient que le strict nécessaire au bon fonctionnement de l’application, à savoir l’application elle-même ainsi que tout son environnement (librairies, compilateur…).

L’avantage principal du conteneur est sa légèreté en termes de place occupée (la plupart du temps moins d’1Go, voir moins de 100Mo) mais aussi en termes de ressources consommées: un conteneur n’a pas besoin de faire tourner son propre noyau et consomme très peu de RAM/CPU.

drawing drawing

L’autre avantage de conteneuriser son application - celui qui nous intéresse le plus ici - c’est qu’il est possible de packager l’application et son environnement d’exécution dans un conteneur. La création de ce conteneur est le plus souvent l’affaire du développeur: plus besoin, pour l’administrateur, de gérer des centaines de librairies possédant chacune plusieurs versions, le tout sur de nombreux serveurs. Il suffit d’installer Docker et de lancer les conteneurs!

Step 3: packaging de notre application

C’est exactement ce que nous allons faire ici: packager notre application. Revenez à VSCode et créez un fichier nommé Dockerfile à la racine de l’application (donc le dossier app) et copiez-collez-y ceci:

FROM node:12-alpine
RUN apk add --no-cache python g++ make
WORKDIR /app
COPY . .
RUN yarn install --production
CMD ["node", "src/index.js"]

Détaillons son contenu:

FROM node:12-alpine

Cette première ligne détermine l’image qui va nous servir de base, sous la forme nom:tag.

Concernant les tags: j’en reparlerai un peu plus loin dans le TP, laissez ça de côté pour l’instant.

Nous utilisons l’image node comme base pour créer la nôtre car elle fournit un environnement adapté à notre application. Ce point est important. Si notre application est en python, en Java ou en C++, il faudra choisir une image plus adaptée.

RUN apk add --no-cache python g++ make

La directive RUN permet de lancer une commande dans le conteneur de base que nous avons choisi. Ici nous faisons appel à apk, le gestionnaire de paquets d’Alpine, pour installer quelques dépendances.

WORKDIR /app

WORKDIR permet de changer le répertoire de travail dans le conteneur.

COPY . .

COPY permet de copier des fichiers de l’extérieur vers l’intérieur du conteneur. En l’occurence, cette commande copie tout ce qui se trouve au niveau du fichier Dockerfile (et en dessous) dans le dossier /app du conteneur.

RUN yarn install --production

RUN permet de lancer une commande dans le conteneur, ici l’installation des dépendances avec yarn.

CMD ["node", "src/index.js"]

CMD est une version spécifique de RUN. Dans l’absolu les deux font la même chose, à savoir exécuter une commande. Mais CMD a pour but d’être la dernière commande du Dockerfile et de lancer notre application. Ainsi, il ne peut y avoir qu’une seule directive CMD par Dockerfile. S’il y en a plusieurs, seule la dernière sera prise en compte.

Nous allons maintenant créer notre image à partir de ce Dockerfile. Lancez la commande suivante (attention à ne pas oublier le point!!) :

docker build -t tpdocker .

Observez ce qu’il se passe: Docker a compris qu’il devait utiliser notre Dockerfile et l’exécute, directive par directive: récupération de l’image source, installation des dépendances…

Step 4: lancement de notre conteneur

Notre application a été packagée dans une image de conteneur, nous allons maintenant créer un conteneur à proprement parler. Voici la commande que vous allez utiliser:

docker run -d -p 3000:3000 tpdocker

docker run crée un conteneur à partir d’une image, ici nommée tpdocker.

Le paramètre -d demande à Docker de lancer le conteneur en arrière plan. Si besoin essayez avec et sans pour bien comprendre.

Le paramètre -p 3000:3000 permet de mapper un port de l’hôte à un port du conteneur (hôte:conteneur). Ici nous allons donc mapper le porte 3000 de l’hôte au port 3000 du conteneur.

Vous pouvez tester avec Firefox que l’application est bien disponible.

Si une image a été crée sans directive CMD ou que l’application lancée par CMD se termine, le conteneur se terminera lui aussi.

Step 5: modification de l’application

Maintenant que l’application est déployée et fonctionnelle, nous allons lui apporter une petite modification, en l’occurence modifier le texte qui apparaît lorsque la liste est vide.

Ouvrez le fichier src/static/js/app.js , trouvez le texte “No item yet! Add one above!” et modifiez-le.

Le code a été modifié, il faut donc:

  • recréer l’image
  • la relancer.

Le message d’erreur indique que le port 3000 est déjà utilisé. En effet, notre premier conteneur tourne toujours!

Utilisez docker ps --all pour afficher la liste des conteneurs qui tournent, vous devriez obtenir quelque chose de ce type:

CONTAINER ID   IMAGE      COMMAND                  CREATED       STATUS       PORTS                                       NAMES
41be103a2ca0   tpdocker   "docker-entrypoint.s…"   2 hours ago   Up 2 hours   0.0.0.0:3000->3000/tcp, :::3000->3000/tcp   competent_stonebraker

Avant de lancer une nouvelle instance, il faut supprimer la première. Utilisez la commande docker stop NOMDUCONTENEUR puis docker rm NOMDUCONTENEUR pour stopper/supprimer le premier conteneur. Vérifiez avec docker ps --all que le conteneur a bien été supprimé, puis créez-en un autre avec la nouvelle image et allez vérifier que vos modifications ont bien été prises en compte.

Step 6: partager votre application

Solution 1: via un dépôt d’images

Les images proviennent, par défaut, d’un dépôt situé à cette adresse: https://hub.docker.com/

Allez y jeter un oeil, vous verrez que ce dépôt principal contient des milliers d’images, dont beaucoup doivent vous dire quelque chose: Node, MySQL, MongoDB…ce sont des composants de base d’applications que vous pouvez utiliser, personnaliser…

Pour des besoins spécifiques, vous trouverez également des distributions “vides”: Ubuntu, Debian, Alpine

Détaillons un peu la page de celle que nous avons utilisé jusqu’à présent, Node: sur la page vous y trouverez une grande quantité de tags (nous avions utilisé le tag 12-alpine): ils vous permettent de choisir la version de Node contenue dans l’image mais aussi la version de l’OS qui sert de base (Alpine, debian stretch, debian buster…).

+Les endroits où envoyer vos images (en utilisant docker push) sont nombreux. Docker Hub est le principal, mais si votre projet est hébergé sur Github/Gitlab, les deux proposent aussi d’héberger vos images.

Solution 2: en distribuant le code source de votre application + le Dockerfile

Un autre solution, tout aussi courante que la première (l’une n’excluant pas l’autre), consiste à déposer sur un dépôt Git le code source de votre application ainsi que le Dockerfile correspondant.

Exemple issue des Docker Samples:

https://github.com/dockersamples/linux_tweet_app

Step 7: mais où sont mes données???

Pour ce test, utilisez l’application dans firefox et ajouter des items.

Puis, supprimez le conteneur.

Enfin, recréez-le.

Sans paramétrage supplémentaire, les données sont stockées dans le conteneur. Si elles survivent lors d’un redémarrage, elles disparaîtront avec le conteneur s’il est détruit.

La solution à ce problème est de paramétrer notre conteneur pour qu’il stocke ses données hors du conteneur, sur la machine hôte.

Solution 1: les bind mounts

La première solution consiste à lier un dossier de l’hôte à un dossier dans le conteneur. Pour cela, il suffit de préciser l’association à Docker lors de la commande docker run, sous cette forme:

docker run -d -p 3000:3000 -v dossierhote:dossierconteneur tpdocker

Il est aussi possible de gérer, pour des cas très précis, fichier par fichier avec la même syntaxe.

Pour en revenir à notre conteneur, supprimez tous les conteneurs qui traînent avec cette commande:

docker stop $(docker ps -a -q)
docker rm $(docker ps -a -q)

Vérifiez que tout est bien parti. Si c’est bon, passez à la suite.

Si vous fouillez dans le code de l’application, vous trouverez dans app/src/persistence/sqlite.js que la base de donnée SQLite est stockée dans /etc/todos/todos.db. C’est ce fichier que nous voulons stocker à l’extérieur du conteneur.

Relancez le conteneur, mais cette fois-ci avec le paramètre -v en plus:

docker run -d -p 3000:3000 -v $(pwd)/todos:/etc/todos tpdocker

Sous Linux, la commande pwd renvoie le chemin complet de l’endroit où vous vous trouvez. En théorie, si vous tapez la commande dans votre shell, vous devriez obtenir /home/ubuntu/Desktop/tpdocker/app. Le $(pwd) est donc remplacé par le résultat de pwd dans notre commande, qui devient donc:

docker run -d -p 3000:3000 -v /home/ubuntu/Desktop/tpdocker/app/todos:/etc/todos tpdocker

Une fois la commande lancée:

Vous pouvez aussi vérifier que le dossier app contient bien un dossier todos avec un fichier todos.db dedans.

Astuce: si vous mettez dans un bind mount le code source de votre application, vous pouvez facilement vous créer un environnement de dev basé sur Docker.

Exemple:

docker run -d -p 3000:3000 -w /app -v "$(pwd):/app" node:12-alpine sh -c "yarn install && yarn run dev"

Une fois le conteneur lancé, l’application va mettre un peu de temps à se lancer (le temps que yarn fasse son boulot).

En attendant, il est possible d’aller voir les logs du conteneur. Récupérez son nom avec docker ps --all et utilisez cette commande:

docker logs -f NOMDUCONTENEUR

Le log devrait afficher quelque chose de ce type:

yarn install v1.22.15
[1/4] Resolving packages...
warning Resolution field "ansi-regex@5.0.1" is incompatible with requested version "ansi-regex@^2.0.0"
warning Resolution field "ansi-regex@5.0.1" is incompatible with requested version "ansi-regex@^3.0.0"
[2/4] Fetching packages...
info fsevents@2.3.2: The platform "linux" is incompatible with this module.
info "fsevents@2.3.2" is an optional dependency and failed compatibility check. Excluding it from installation.
[3/4] Linking dependencies...
[4/4] Building fresh packages...
Done in 14.99s.
yarn run v1.22.15
$ nodemon src/index.js
[nodemon] 2.0.13
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node src/index.js`
Using sqlite database at /etc/todos/todo.db
Listening on port 3000

nodemon, mentionné ici dans les logs, est un outil qui permet de scruter les changements du code source et de relancer Node si besoin.

En l’occurence, ouvrez le fichier app/src/static/js/app.js, modifiez le libellé du bouton Add Item (ligne ~109). Enregistrez les modifications, regardez les logs: vous devriez voir passer un message vous disant que les sources ont été actualisées.

Solution 2: les volumes

Les volumes sont des espaces de stockage gérés par Docker. L’approche est très similaire à celle des bind mounts, mais le fonctionnement en arrière plan est un peu plus complexe.

Contrairement aux bind mounts, il suffit de préciser un nom de volume, l’emplacement est géré par Docker.

Une fois n’est pas coutume, supprimez le conteneur. Recréez-le ensuite avec cette commande:

docker run -d -p 3000:3000 -v todos-db:/etc/todos tpdocker

Vous avez vu la différence? Le chemin côté hôte ($(pwd)/todos) a été remplacé par un simple nom de volume, todos-db.

Pour en savoir plus sur notre volume (par exemple où il se trouve), utilisez ces deux commandes:

docker volume list
docker volume inspect NOM

Dans notre cas, docker volume inspect todos-db devrait vous donner quelque chose de ce type:

[
    {
        "CreatedAt": "2021-11-05T13:36:34+01:00",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/todos-db/_data",
        "Name": "todos-db",
        "Options": null,
        "Scope": "local"
    }
]

Une partie intéressante dans ces informations: le point de montage est précisé et vous permet de savoir où sont stockées vos données.

Step 8: les applications multi-conteneurs

Pour l’instant, notre application toute entière tourne dans un seul conteneur. Mais dans le monde de la virtualisation il existe une règle qui dit qu’un conteneur est dédié à une tâche et une seule.

La liste peur être très longue, mais principalement:

  • si vos services sont séparés, le crash de l’un n’entraînera pas forcément le crash de l’autre.
  • il est bien plus facile de redémarrer des conteneurs lorsque leurs services sont bien distincts.
  • des services distincts peuvent monter en version plus facilement
  • deux services dans un même conteneur peuvent entrer en concurrence (en termes de ressources CPU par exemple) et finir par se marcher l’un sur l’autre.
  • globalement il est plus facile de mettre en production une application morceaux par morceaux plutôt que d’un bloc.

En l’occurence, ce n’est pas notre cas: notre conteneur se charge de faire tourner notre appli avec Node, mais aussi de la partie base de données.

Dans cette partie nous allons y remédier.

La mise en réseau des conteneurs

Tout comme nous avons crée un named volume tout à l’heure, Docker nous permet aussi (de tout aussi simplement) de créer des réseaux nommés. La règle est simple: deux conteneurs sur un même réseau pourront se parler, deux conteneurs sur deux réseaux différents ne pourront pas. Simple et efficace.

Commencez par créer le réseau:

docker network create todo-app

Ensuite, créez un second conteneur pour MySQL:

docker run -d --network todo-app --network-alias mysql -v todo-mysql-data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=secret -e MYSQL_DATABASE=todos mysql:5.7

Ok, la commande est assez longue, prenons le temps de la décortiquer:

Lancement d’un conteneur MySQL

Revenons sur les variables d’environnement. Certaines images peuvent être configurées à l’aide de ces variables. Lorsque c’est le cas, la documentation de l’image le précisera. Pour MySQL par exemple, regardez dans la section Environment Variables:

MySQL - Docker Hub

Pour résumer, celles que nous utilisons permettent de donner une valeur au mot de passe root et de préciser avec quelle base de données nous allons travailler.

Pour vérifier que tout est en ordre, nous allons entrer dans le conteneur. Pour ce faire, récupérez le nom du conteneur MySQL, et lancez la commande suivante:

docker exec -it NOMDUCONTENEUR sh

docker exec, comme son nom l’indique, exécute une commande (ici sh, le shell de notre conteneur) et nous permet d’interagir avec (grâce à -it).

Vous obtenez ainsi un invite de commande basique dans le conteneur.

#

Nous allons maintenant nous connecter au serveur MySQL qui tourne dans ce conteneur, avec la commande suivante:

mysql -u root -p

-u précise l’utilisateur, -p qu’il faut nous demander un mot de passe. Le mot de passe en question a été paramétré un peu plus haut, vous vous souvenez?

Si tout se passe bien, vous obtenez un nouvel invite:

mysql>

Là, nous allons demander à MySQL de nous afficher les bases de données existantes. Tapez simplement SHOW DATABASES; (attention au ; !!), validez, et vérifiez que la base de données todos est bien présente:

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
| todos              |
+--------------------+

Paramétrer notre application pour MySQL

Notre conteneur pour notre base de données fonctionne bien, il faut maintenant paramétrer notre application pour qu’elle l’utilise.

Pour ça, commencez par supprimer l’ancien conteneur. Attention à ne pas supprimer le conteneur MySQL cette fois-ci!!!

Une fois de plus, nous allons utiliser des variables d’environnement pour préciser les infos de connexion à la BDD:

docker run -dp 3000:3000 \
   -w /app -v "$(pwd):/app" \
   --network todo-app \
   -e MYSQL_HOST=mysql \
   -e MYSQL_USER=root \
   -e MYSQL_PASSWORD=secret \
   -e MYSQL_DB=todos \
   node:12-alpine \
   sh -c "yarn install && yarn run dev"

Si vous regardez les logs du conteneur de notre appli, vous devriez voir du changement:

[nodemon] 2.0.13
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node src/index.js`
Waiting for mysql:3306.
Connected!
Connected to mysql db at host mysql
Listening on port 3000

Step 9: docker compose

Jusqu’à présent nous avons défini les paramètres de nos conteneurs dans les paramètres des lignes de commandes que nous avons utilisées, principalement dans docker run.

Pour lancer rapidement un conteneur simple, cette approche est valable. Mais dès qu’il s’agit d’en lancer plusieurs avec de nombreux paramètres, elle peut devenir fastidieuse et source d’erreurs.

Pour résoudre ce problème, Docker possède un composant supplémentaire: docker-compose.

La démarche est simple:

Nous allons donc, dans ce qui suit, convertir les commandes qui permettent de lancer nos deux conteneurs en un seul fichier de configuration.

La base du fichier.

Dans le dossier app, créez un fichier nommé docker-compose.yml.

Copiez-y ces lignes:

version: "3.7"

services:

La première ligne permet de définir la version du schema de docker-compose. La version du langage de définition des paramètres des conteneur si vous voulez. Ces paramètres évoluent avec le temps, c’est donc une bonne idée de préciser quelle version vous utilisez.

La ligne services: marque le début de la liste des conteneurs que nous allons définir.

Mais avant de continuer…

Définition du service pour l’application

Nous allons donc convertir la commande docker-run que nous avons utilisée en dernier et compléter petit à petit notre fichier docker-compose.

docker run -dp 3000:3000 \
   -w /app -v "$(pwd):/app" \
   --network todo-app \
   -e MYSQL_HOST=mysql \
   -e MYSQL_USER=root \
   -e MYSQL_PASSWORD=secret \
   -e MYSQL_DB=todos \
   node:12-alpine \
   sh -c "yarn install && yarn run dev"

Ajoutez sous la directive services un service nommé app.

version: "3.7"

services:
  app:

Puis ajoutez le nom de l’image à utiliser:

version: "3.7"

services:
  app:
    image: node:12-alpine
docker run -dp 3000:3000 \
   -w /app -v "$(pwd):/app" \
   --network todo-app \
   -e MYSQL_HOST=mysql \
   -e MYSQL_USER=root \
   -e MYSQL_PASSWORD=secret \
   -e MYSQL_DB=todos \
   sh -c "yarn install && yarn run dev"

Ajoutons ensuite la commande à lancer dans notre conteneur, sous la forme d’une directive command:

version: "3.7"

services:
  app:
    image: node:12-alpine
    command: sh -c "yarn install && yarn run dev"
docker run -dp 3000:3000 \
   -w /app -v "$(pwd):/app" \
   --network todo-app \
   -e MYSQL_HOST=mysql \
   -e MYSQL_USER=root \
   -e MYSQL_PASSWORD=secret \
   -e MYSQL_DB=todos \
   sh -c "yarn install && yarn run dev"

Puis les ports, avec la commande ports:

version: "3.7"

services:
  app:
    image: node:12-alpine
    command: sh -c "yarn install && yarn run dev"
    ports:
      - 3000:3000
docker run -dp 3000:3000 \
   -w /app -v "$(pwd):/app" \
   --network todo-app \
   -e MYSQL_HOST=mysql \
   -e MYSQL_USER=root \
   -e MYSQL_PASSWORD=secret \
   -e MYSQL_DB=todos

…le répertoire de travail et le volume, avec les commandes working_dir et volumes:

version: "3.7"

services:
  app:
    image: node:12-alpine
    command: sh -c "yarn install && yarn run dev"
    ports:
      - 3000:3000
    working_dir: /app
    volumes:
      - ./:/app
docker run -d
   -w /app -v "$(pwd):/app" \
   --network todo-app \
   -e MYSQL_HOST=mysql \
   -e MYSQL_USER=root \
   -e MYSQL_PASSWORD=secret \
   -e MYSQL_DB=todos

Enfin, il ne reste plus que les variables d’environnement:

version: "3.7"

services:
  app:
    image: node:12-alpine
    command: sh -c "yarn install && yarn run dev"
    ports:
      - 3000:3000
    working_dir: /app
    volumes:
      - ./:/app
    environment:
      MYSQL_HOST: mysql
      MYSQL_USER: root
      MYSQL_PASSWORD: secret
      MYSQL_DB: todos
docker run -d
   --network todo-app \
   -e MYSQL_HOST=mysql \
   -e MYSQL_USER=root \
   -e MYSQL_PASSWORD=secret \
   -e MYSQL_DB=todos

Définition du service pour MySQL

Même procédure pour l’autre conteneur. Ajoutez un service mysql à la suite de app (et au même niveau d’indentation!), et ajoutez-y la directive image:

version: "3.7"

services:
  app:
    image: node:12-alpine
    command: sh -c "yarn install && yarn run dev"
    ports:
      - 3000:3000
    working_dir: /app
    volumes:
      - ./:/app
    environment:
      MYSQL_HOST: mysql
      MYSQL_USER: root
      MYSQL_PASSWORD: secret
      MYSQL_DB: todos
  mysql:
    image: mysql:5.7
docker run -d \
  --network todo-app --network-alias mysql \
  -v todo-mysql-data:/var/lib/mysql \
  -e MYSQL_ROOT_PASSWORD=secret \
  -e MYSQL_DATABASE=todos \
  mysql:5.7

Nous allons ensuite ajouter le volume avec la commande éponyme. Mais ici, petite subtilité: vu qu’un volume peut être commun à plusieurs services au sein d’une stack, nous allons le définir tout en bas du fichier, à part:

version: "3.7"

services:
  app:
    image: node:12-alpine
    command: sh -c "yarn install && yarn run dev"
    ports:
      - 3000:3000
    working_dir: /app
    volumes:
      - ./:/app
    environment:
      MYSQL_HOST: mysql
      MYSQL_USER: root
      MYSQL_PASSWORD: secret
      MYSQL_DB: todos
  mysql:
    image: mysql:5.7
    volumes:
      - todo-mysql-data:/var/lib/mysql

volumes:
  todo-mysql-data:
docker run -d \
  --network todo-app --network-alias mysql \
  -v todo-mysql-data:/var/lib/mysql \
  -e MYSQL_ROOT_PASSWORD=secret \
  -e MYSQL_DATABASE=todos

Terminez avec les variables d’environnement:

version: "3.7"

services:
  app:
    image: node:12-alpine
    command: sh -c "yarn install && yarn run dev"
    ports:
      - 3000:3000
    working_dir: /app
    volumes:
      - ./:/app
    environment:
      MYSQL_HOST: mysql
      MYSQL_USER: root
      MYSQL_PASSWORD: secret
      MYSQL_DB: todos
  mysql:
    image: mysql:5.7
    volumes:
      - todo-mysql-data:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: secret
      MYSQL_DATABASE: todos

volumes:
  todo-mysql-data:
docker run -d \
  --network todo-app --network-alias mysql \
  -e MYSQL_ROOT_PASSWORD=secret \
  -e MYSQL_DATABASE=todos

Notre fichier est terminé!

Lancer la stack

Avant de lancer notre stack, supprimez tous les conteneurs qui traînent.

Ensuite, lancez-la avec cette commande:

docker-compose up -d

up → lance la stack -d → en arrière plan.

Normalement, vous devriez obtenir des messages de ce type:

Creating network "app_default" with the default driver
Creating volume "app_todo-mysql-data" with default driver
Creating app_mysql_1 ... done
Creating app_app_1   ... done

Comme vous le voyez, Docker:

Allez vérifier que votre appli fonctionne. Si besoin: docker ps --all et docker logs -f, comme d’habitude.

Supprimer la stack

Vous avez

docker-compose up -d

pour lancer une stack, pour la supprimer vous avez:

docker-compose down

Bonus

Plugin Docker pour VSCode

Il existe un plugin Docker pour VSCode qui peut très largement vous aider dans l’exploitation de Docker:

N’hésitez pas (une fois que vous avez terminé le TP!), c’est gratuit!

Play with docker

Play with docker est un outil en ligne vraiment sympa qui vous permet de tester Docker en ligne, de créer plusieurs conteneurs…le tout gratuitement.

Play with Docker

Récapitulatif des commandes utilisées

# créer une image à partir d'un Dockerfile
docker build -t NOMIMAGE .

# Créer un conteneur à partir d'une image
docker run -d -p 3000:3000 NOMIMAGE

# Stopper tous les conteneurs
docker stop $(docker ps -a -q)

# Supprimer tous les conteneurs
docker rm $(docker ps -a -q)

# Afficher en continu les logs d'un conteneur
docker logs -f NOMDUCONTENEUR

# Lister les volumes
docker volume list

# Inspecter un volume
docker volume inspect NOM

# Créer un réseau
docker network create NOMRZO

# Exécuter une commande dans un conteneurs
docker exec -it NOMDUCONTENEUR COMMANDE

# Lancer une stack contenue dans un fichier docker-compose.yml
docker-compose up -d

# Supprimer cette même stack
docker-compose down

Pour aller plus loin

Sauvegarder des named volumes

Comme je l’ai dit ici, la sauvegarde des named volumes est un poil plus complexe qu’avec les bind mounts. Plusieurs stratégies sont possibles.

La plus simple est d’utiliser les named volumes pour ce pour quoi ils ont été prévus à la base: avec un stockage déporté. L’idéal est d’avoir, dans votre infrastructure, un espace de stockage partagé (NFS, SMB…) sur lequel vos conteneurs iront mettre leurs données. Dans ce cas, la sauvegarde du stockage déporté est réalisée par un service dédié.

Sinon il existe une méthode pour sauvegarder un named volume: le monter dans un conteneur qui se chargera de faire le sauvegarde. La méthode est détaillée ici:

Use volumes

Docker et après

Docker est le première brique d’un écosystème extrêmement vaste. Il nous permet de faire tourner notre appli et sa base de donnée sur un serveur. Mais si votre appli est utilisée par des milliers de personnes, un seul serveur risque de ne plus suffire. De même: si le serveur de BDD cesse de fonctionner, est-il possible d’un autre prenne le relais pour éviter que les utilisateurs n’aient plus accès à l’application? Et si j’ai des serveurs partout dans le monde?

Tout ça, c’est le job d’un orchestrateur. Il en existe beaucoup, mais les principaux sont Docker Swarm, Kubernetes et Nomad. Le job est, dans les grandes lignes:

Le monde des orchestrateurs va très largement au delà de cette introduction à Docker. Avant de vous y jeter, assurez-vous de bien comprendre Docker.

Si vous voulez aller plus loin sur le sujet, je vous conseille cette chaîne Youtube:

TechWorld with Nana

Sources

Ce TP est très largement inspiré du Getting Started de Docker que vous pourrez retrouver ici:

Orientation and setup