Leçon 1h

4.3 Premier Docker Compose

Créons notre premier Docker Compose en ajoutant une base de données et une interface pour l'administrer et une application

Maintenant, passons à la pratique de manière chirurgicale ! Pour cela, nous allons simplement utiliser Docker Compose pour lancer notre application, une base de données et une application qui va permettre de visualiser cette base de données. Dans notre cas, on va utiliser MySQL 8.0 et phpMyAdmin, qui sont deux grands classiques.

Dans un premier temps, nous allons définir un plan d'action comme nous aurions pu le faire lors de la création de notre Dockerfile pour l'application, sauf qu'ici, nous allons devoir schématiser cela de manière un peu plus large.

Voici un exemple d'un diagramme technique très simple :

On y voit donc une application Python (FastAPI dans notre cas), une application phpMyAdmin qui permet d'avoir une interface UI pour la base de données, et une base de données MySQL.

On peut voir que les deux applications sont reliées à la base de données par connexion TCP (Transmission Control Protocol). C'est le protocole standard utilisé pour faire communiquer les applications entre elles en réseau. Le réseau interne créé par Docker n'étant pas exposé directement sur Internet, personne de l'extérieur ne peut intercepter les informations qui circulent entre vos conteneurs en interne.

Revenons à nos moutons : nous avons maintenant une vue d'ensemble simple et rapide pour savoir comment nous allons conteneuriser tout cela efficacement !

Comme pour le cours sur la création d'un Dockerfile, nous allons découper cela en plusieurs morceaux digestes et logiques.


Le service applicatif

Voici ce que l'on sait du service applicatif :

  • Développé en Python avec FastAPI -> port 8000 généralement.
  • Nécessite une base de données pour fonctionner -> variables d'environnement à injecter.
  • Pas de rétention d'information de son côté -> pas de volume à monter.

Avec ces informations, nous pouvons traduire ce service comme suit :

services:
  python:
    image: kernellab/fastapi_docker:latest
    env_file:
      - .env
    ports:
      - 8000:8000

Voilà un service Docker Compose des plus simples : on utilise l'image construite lors du cours précédent, on charge le fichier .env situé à la racine (au même niveau que le fichier compose.yaml), et on mappe le port 8000 du conteneur sur le port 8000 de notre machine hôte. Maintenant, passons au service de base de données, le plus critique.


Le service de base de données MySQL

Décortiquons également ce dont nous allons avoir besoin pour bien comprendre :

  • C'est du MySQL, donc le port standard est le 3306.
  • Utilisation de variables d'environnement -> nécessitera un fichier .env.
  • La base de données doit pouvoir conserver ses informations en cas de redémarrage -> nous allons devoir monter un volume.

Pour trouver nos informations et comprendre où monter le volume, nous nous rendons sur la documentation officielle Docker Hub de MySQL.

Grâce à cette documentation, nous obtenons plusieurs informations clés :

  • Le volume doit être monté sur l'emplacement /var/lib/mysql.
  • Variables d'environnement nécessaires pour lancer MySQL :
    • MYSQL_ROOT_PASSWORD : Le mot de passe root
    • MYSQL_USER : L'utilisateur applicatif
    • MYSQL_PASSWORD : Le mot de passe de l'utilisateur
    • MYSQL_DATABASE : La base de données créée par défaut

Ce sont des informations très précises et indispensables à connaître, sans quoi le conteneur plantera au démarrage pour différentes raisons pas toujours évidentes à comprendre au début.

Voici à quoi ressemble notre service de base de données :

database:
  image: mysql:8.0
  env_file:
    - .env
  ports:
    - 3306:3306
  volumes:
    - database:/var/lib/mysql

Persistance et volumes Docker

Le volume monté sert d'espace de stockage persistant pour les données de votre conteneur. Dans notre situation, toutes les informations écrites dans /var/lib/mysql à l'intérieur du conteneur seront conservées sur la machine hôte.

Attention : les conteneurs et les volumes peuvent occuper beaucoup de place sur votre machine. Pensez de temps en temps à nettoyer votre environnement avec docker system prune !


Le service phpMyAdmin

Ce service va être également très simple car il ne contient pas de volume particulier et sert juste à se connecter à notre base de données en TCP via l'interface web pour visualiser les tables, les bases de données et interagir avec elles.

Pour ce faire, nous avons besoin de :

  • Une visibilité web : mapper le port 8080 de l'hôte sur le port 80 du conteneur.

En consultant la documentation Docker Hub de phpMyAdmin, nous trouvons une configuration prête à l'emploi :

phpmyadmin:
  image: phpmyadmin
  restart: always
  ports:
    - 8080:80
  environment:
    - PMA_ARBITRARY=1
En développement, on laissera PMA_ARBITRARY=1 pour pouvoir modifier des paramètres à la volée comme l'IP, le serveur hôte... Mais en production, ne laissez jamais cette option active.

Maintenant que nous avons nos 3 services proprement découpés, nous allons les assembler dans un seul fichier !


L'assemblage des services

Pour cela, vous allez devoir créer un fichier nommé compose.yaml à la racine de votre repository / dossier, de manière à avoir les fichiers d'environnement au bon endroit.

Une fois votre fichier créé, nous allons y ajouter nos 3 services sous le mot-clé principal services: :

services:
  python:
    image: kernellab/fastapi_docker:latest
    env_file:
      - .env
    ports:
      - 8000:8000

  database:
    image: mysql:8.0
    env_file:
      - .env
    ports:
      - 3306:3306
    volumes:
      - database:/var/lib/mysql

  phpmyadmin:
    image: phpmyadmin
    restart: always
    ports:
      - 8080:80
    environment:
      - PMA_ARBITRARY=1

Là, on commence à avoir quelque chose de propre ! Cependant, lancé tel quel, le Docker Compose ne marchera pas : il nous manque 2 éléments essentiels et 1 optionnel que nous allons mettre en place.

1. La déclaration du volume

Il existe deux façons de déclarer des volumes :

  1. ./database:/var/lib/mysql -> Bind Mount : lie un dossier de notre projet nommé database pour partager des fichiers en temps réel entre le conteneur et la machine hôte (très pratique en développement pour le hot reload).
  2. database:/var/lib/mysql -> Volume nommé (Named Volume) : Docker crée un volume géré par le système pour stocker des informations persistantes (très recommandé en production et pour le stockage de bases de données).

Dans notre cas, nous allons utiliser le volume nommé pour la base de données (option n°2). Il faudra donc déclarer ce volume à la racine du fichier Compose sous la clé volumes:.

2. Création du fichier de variables d'environnement (.env)

Pour lancer nos applications, nous avons besoin de variables d'environnement pour configurer les identifiants et accès.

Pour notre application Python, nous aurons besoin de :

  • Le nom de la base de données
  • Le port de la base de données
  • L'utilisateur de la base de données
  • Le mot de passe de la base de données

Règle d'or de sécurité

On n'utilisera jamais l'utilisateur root directement pour une application : ce serait une hérésie en matière de sécurité !

Pour notre service de base de données MySQL :

  • Le mot de passe pour le compte root
  • Le nom de la base de données à créer
  • Un utilisateur pour l'application
  • Un mot de passe pour l'utilisateur

Créons notre fichier .env :

DB_USER=kernellab
DB_PASSWORD=kernellab123
DB_HOST=database
DB_PORT=3306
DB_ROOT_PASSWORD=unmotdepasseroot
DB_DATABASE=kernellab

Bonne pratique .gitignore

Pensez à ajouter votre fichier .env dans votre .gitignore et .dockerignore pour ne jamais versionner ni commit de données sensibles (clés d'API, mots de passe...).

Le fichier .env est un simple fichier de paires CLÉ=VALEUR. Notre rôle sera de mapper ces valeurs dans le fichier Docker Compose avec la directive environment: :

services:
  python:
    image: kernellab/fastapi_docker:latest
    env_file:
      - .env
    ports:
      - 8000:8000

  database:
    image: mysql:8.0
    environment:
      - MYSQL_ROOT_PASSWORD=${DB_ROOT_PASSWORD:-root}
      - MYSQL_DATABASE=${DB_DATABASE:-fastapi_docker}
      - MYSQL_USER=${DB_USER:-fastapi_user}
      - MYSQL_PASSWORD=${DB_PASSWORD:-fastapi_password}
    ports:
      - 3306:3306
    volumes:
      - database:/var/lib/mysql

  phpmyadmin:
    image: phpmyadmin
    restart: always
    ports:
      - 8080:80
    environment:
      - PMA_ARBITRARY=1

volumes:
  database:
  • MYSQL_ROOT_PASSWORD : La variable officielle attendue par l'image MySQL.
  • DB_ROOT_PASSWORD : Notre variable injectée dynamiquement depuis le fichier .env.
  • :-root : La valeur de repli par défaut si la variable n'est pas fournie dans le .env.
  • ${} : La syntaxe d'interpolation dynamique de Docker Compose.

3. Création du réseau interne (Networks)

Passons à la gestion du réseau Docker : c'est une notion essentielle qui permet de sécuriser vos applications à travers un système de réseaux internes pour isoler les services entre eux. Il existe plusieurs types de réseaux :

Type de RéseauCas d'usage principalPortée
BridgeApplications web standards, bases de donnéesUne seule machine (Hôte local)
OverlayClusters Docker Swarm, haute disponibilitéMulti-machines (Serveurs multiples)
HostPerformances réseau maximales (ex: streaming)Partage le réseau de la machine hôte
NoneTâches de traitement sécurisées hors ligneIsolation totale (pas de réseau)

1. Bridge (Le réseau par défaut)

C'est le réseau créé automatiquement quand vous lancez docker compose up. Il agit comme un switch virtuel privé à l'intérieur de votre machine.

  • Avantage : Vos conteneurs peuvent communiquer entre eux via leurs noms de service (ex: python peut joindre database), tout en restant isolés du reste de votre machine sauf si vous exposez des ports (ports:).

2. Overlay (Le réseau distribué)

Il est utilisé quand votre application tourne sur plusieurs serveurs différents (généralement via Docker Swarm).

  • Avantage : Il crée un tunnel sécurisé entre plusieurs machines physiques.

3. Host (La performance avant tout)

Le conteneur n'est plus isolé sur le plan réseau : il utilise directement l'adresse IP et les ports de la machine hôte.

  • Avantage : Supprime la latence liée à l'isolation réseau de Docker.

4. None (La chambre forte)

Le conteneur est complètement déconnecté de tout réseau (aucun accès à Internet ni aux autres conteneurs).

  • Avantage : Parfait pour exécuter des scripts hautement sensibles ou isolés en toute sécurité.

Le Docker Compose final sécurisé

Ce qui va nous intéresser ici, c'est d'utiliser le réseau Bridge pour isoler nos flux :

  1. Notre backend Python doit communiquer avec la base de données MySQL (backend_network).
  2. phpMyAdmin doit communiquer avec la base de données MySQL (database_network).
  3. phpMyAdmin n'a jamais besoin de communiquer avec l'application Python.

En créant deux réseaux distincts, nous cloisonnons les accès !

services:
  python:
    image: kernellab/fastapi_docker:latest
    depends_on:
      - database
    networks:
      - backend_network
    env_file:
      - .env
    ports:
      - 8000:8000

  database:
    image: mysql:8.0
    networks:
      - database_network
      - backend_network
    environment:
      - MYSQL_ROOT_PASSWORD=${DB_ROOT_PASSWORD:-root}
      - MYSQL_DATABASE=${DB_DATABASE:-fastapi_docker}
      - MYSQL_USER=${DB_USER:-fastapi_user}
      - MYSQL_PASSWORD=${DB_PASSWORD:-fastapi_password}
    ports:
      - 3306:3306
    volumes:
      - database:/var/lib/mysql

  phpmyadmin:
    image: phpmyadmin
    depends_on:
      - database
    restart: always
    networks:
      - database_network
    ports:
      - 8080:80
    environment:
      - PMA_ARBITRARY=1

volumes:
  database:

networks:
  backend_network:
    driver: bridge
  database_network:
    driver: bridge
L'instruction depends_on: [database] ajoutée sur python et phpmyadmin indique à Docker Compose d'attendre le lancement du service de base de données avant de démarrer les services dépendants. Cela évite les plantages intempestifs si l'application démarre avant que MySQL ne soit initialisé.