Cet article a pour but de vous présenter Docker Compose et la manière dont je m’en sers au quotidien.

Lorsque je travaille sur une application web, Docker est devenu un automatisme. Tant pour uniformiser les environnements (aussi incroyable que ça puisse paraître, certains d’entre nous travaillent sur Windows) que pour éviter les surprises au passage en production, c’est un gain de temps et d’énergie dont je ne peux plus me passer.

Au départ, Docker ne me servait qu’à gérer l’environnement de travail en local : les dépendances comme PostgreSQL ou Redis étaient assurées par ma machine car je les avais déjà à ma disposition. Puis est venu le moment où nous avons dû faire tourner localement plusieurs versions différentes de PostgreSQL, utiliser des outils pénibles à installer sur macOS, gérer des dépendances non compatibles entre-elles, etc. Du jour au lendemain, Docker était partout sur ma machine ! Mais je n’étais pas satisfait car je devais sans cesse démarrer et éteindre manuellement des conteneurs, parfois même en ratant une commande qui perdait toutes les données d’une de mes bases locales… J’ai alors cherché un moyen de gérer des conteneurs comme un ensemble cohérent, et j’ai découvert Docker Compose. Après de longs mois passés à l’utiliser au quotidien, il est temps pour moi de vous en parler.

Docker Compose, qu’est-ce que c’est ?

Docker Compose est un outil qui permet de décrire (dans un fichier YAML) et gérer (en ligne de commande) plusieurs conteneurs comme un ensemble de services inter-connectés. Si je travaille sur une application Rails, je vais par exemple décrire un ensemble composé de 3 conteneurs :

  • un conteneur PostgreSQL
  • un conteneur Redis
  • un conteneur pour le code de mon application

Je pourrai alors démarrer mon ensemble de conteneurs en une seule commande docker-compose up. Sans Docker Compose, j’aurais dû lancer 3 commandes docker run avec beaucoup d’arguments pour arriver au même résultat. Par ailleurs, cela aurait nécessité que je rédige un README plutôt précis pour que les autres membres de mon équipe obtiennent le même résultat. Avec Docker Compose, cette configuration est faite dans un fichier qui est versionné avec le reste du code de l’application.

Dans le fichier docker-compose.yml, chaque conteneur est décrit avec un ensemble de paramètres qui correspondent aux options disponibles lors d’un docker run : l’image à utiliser, les volumes à monter, les ports à ouvrir, etc. Mais on peut également y décrire des éléments supplémentaires, comme la possibilité de « construire » (docker build) une image à la volée avant d’en lancer le conteneur.

Mais passons sans plus attendre à un exemple concret !

Exemple d’une configuration Docker Compose

voici un exemple de fichier docker-compose.yml :

Dans cet exemple, on voit que j’ai décrit 4 services :

1. Un conteneur postgres fournissant PostgreSQL.

Je lui fais utiliser l’image publique postgres (version 10).
Je lui précise également 3 variables d’environnement pour pouvoir m’y connecter par la suite.

2. Un conteneur redis fournissant Redis.

Je lui fais utiliser l’image publique redis (version 3.2, variante alpine).

3. Un conteneur rails fournissant mon application

Je lui demande de construire une image basée sur mon répertoire local.
Je le fais dépendre des deux premiers services postgres et redis, afin qu’il ne soit construit que lorsque ceux-ci le sont avec succès.
Je lui précise un DATABASE_URL pour utiliser le conteneur postgres comme base de données, et un REDIS_HOST pour utiliser le conteneur redis comme base Redis.
Je monte enfin en volume mon répertoire local pour que mes changements de code soient directement disponibles au sein du conteneur sans avoir à le reconstruire.

4. Un conteneur nginx fournissant un point d’entrée sur le port 3000.

Je lui fais utiliser l’image publique nginx (dernière version disponible).
Je le relie au service rails pour pouvoir utiliser ce nom d’hôte au sein de la configuration de nginx.
Je lui ouvre le port 3000 de ma machine et lui demande de le relier à son port 80 interne.
Je lui fournis un fichier de configuration.

Vous remarquerez peut-être que la variable d’environnement DATABASE_URL utilise le nom d’hôte postgres pour faire référence au conteneur correspondant. Ceci fonctionne car j’ai mis le service postgres dans la liste depends_on de mon service, ce qui crée un pointage au sein du réseau créé par Docker entre mes conteneurs.

Quelques commandes utiles

Au quotidien, voici les commandes que j’utilise le plus :

  • docker-compose up
  • docker-compose down
  • docker-compose restart
  • docker-compose exec
  • docker-compose logs

docker-compose up démarre les services décrits dans mon docker-compose.yml et ne me rend pas la main.
docker-compose up -d fait la même chose mais me rend la main une fois que les services sont démarrés.
docker-compose up –build reconstruit les services avant de les lancer.

docker-compose down stoppe les services.

docker-compose restart redémarre l’ensemble des services.
docker-compose restart nginx redémarre un des services (ici nginx).

docker-compose exec rails bash me fournit une console bash au sein du conteneur rails.
docker-compose exec rails bin/rails db:migrate effectue un rails db:migrate au sein du conteneur rails.

docker-compose logs me retourne l’ensemble des logs des services depuis le dernier démarrage et me rend la main.
docker-compose logs -f affiche les logs des services et continue à les « écouter » sans me rendre la main.
docker-compose logs -f rails fait la même chose pour le conteneur rails uniquement.

Le mot de la fin

J’espère vous avoir convaincu de l’utilité de cet outil, et je vous invite à le tester sans plus attendre afin de vous faire votre propre avis. Si vous avez besoin de conseils, n’hésitez à m’écrire pour en parler !