docker-compose¶
Applications multi-containers¶
Besoin¶
Pour concevoir et déployer des applications fondées sur plusieurs micro-services :
BD
NoSQL (Mongo etc.)
Applications
APIs
de nouveaux besoins apparaissent :
nécessité de communiquer entre containers
possibilité de créer des réseaux ad-hoc mais pas très facile à manipuler
besoin de pouvoir décrire dans une syntaxe simplifiée un système de containers - avec des images prédéfinies - ou spécifiées dans de Dockerfiles - communicant naturellement entre elles
Solution : docker-compose¶
La commande docker-compose repose sur un fichier docker-compose.yml, écrit au format YAML.
Format YAML¶
json en moins verbeux
plus lisible, avec identation (2 caractères décalage suffisent)
bcp de fichiers de conf utilisent ce format
cf exemples
docker ou docker-compose ?¶
Créons un conteneur nginx avec la commande Docker :
docker run --name web -d -p 8000:80 nginx:alpine
Puis allez visiter http://localhost:8000 …
On peut vouloir monter un volume dans notre container pour publier une page de notre cru via nginx :
créer un dossier de base nommé compose-nginx
créer dedans dossier app contenant un fichier index.html de base du style :
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Nginx Docker</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css">
</head>
<body>
<section class="section">
<div class="container">
<h1 class="Nginx via Docker">
Hello World
</h1>
<p class="subtitle">
Nginx à l'intérieur d'un container <strong>Docker</strong>!
</p>
</div>
</section>
</body>
</html>
Puis relançons la commande en montant app au bon endroit :
docker run --name web -itd -p 8000:80 -v $(pwd)/app:/usr/share/nginx/html nginx:alpine
Nginx avec docker-compose¶
Faisons plus simple avec une description en yaml :
version: '3.9'
services:
web:
image: nginx:alpine
ports:
- "8000:80"
volumes:
- ./app:/usr/share/nginx/html
Explications¶
version entre 2 et 3.9. Mettez 3 ou 3.3 si version de docker pas très à jour …
top level : services
ici un seul service : web, assuré par nginx
le volume local app sera visible dans le container, à l’emplacement /usr/share/nginx/html
le port 80 de l’intérieur du container sera visible dans l’hôte sur http://localhost:8000
Lancement : docker-compose up -d en mode detached
Questions¶
Peut-on changer en direct le contenu du fichier index.html du dossier app pendant que le conteneur tourne ?
comment lister ce qui tourne ?
Quels volumes sont montés ?
Comment tout arrêter ?
Services¶
On peut évidemment placer de mutiples services dans un docker-compose.
les différents services sont décrits avec le même niveau d’indenation.
les services peuvent se décrire avec une image préfabriquée (Dockerhub) * avec d’éventuels fichiers de configuration * avec d’éventuels paramètres
les services peuvent également faire référence à des images fabriquées avec des Dockerfile spécifiques
principales commandes docker-compose¶
Commandes |
Utilisation |
---|---|
|
build |
|
lancer l’app |
|
lancer en arrière plan |
|
lister les containers de l’app |
|
visualiser les logs du container nginx |
|
faire une pause en gardant les containers en l’état |
|
arrêter la pause |
|
arrêter l’application en gardant les données associées |
|
arrêter l’application en enlevant les containers, réseaux et volumes associés |
|
grand ménage ! (Attention données, etc.) |
Testez-les !
Postgresql avec adminer¶
Une première application muti-container où l’on se contente d’images prédéfinies.
Le docker-compose¶
# Use postgres/example user/password credentials
version: '3.1'
services:
db:
image: postgres
restart: always
environment:
POSTGRES_PASSWORD: example
adminer:
image: adminer
restart: always
ports:
- 8080:8080
Placez le dans un répertoire séparé et testez !
Quels sont les services ?
A quoi correspondent les directives restart: always` et environment` ?
Adminer tourne-t-il sur son port par défaut ?
Exemple multi-containers avec Dockerfile : Flask et Redis¶
Exemple classique
Redis est un sytème clef/valeur efficace dans le Cloud
Flask est un micro-framework Python pour développer simplement des app Web
Tout le code de Flask est dans un seul fichier run.py
Créer un répertoire compose-flask-redis contenant le fichier suivant
Le fichier run.py¶
from flask import Flask
from redis import Redis
app = Flask(__name__)
redis = Redis(host='redis', port=6379)
@app.route('/')
def hello():
count = redis.incr('hits')
return 'Hello for the {} time !\n'.format(count)
if __name__ == "__main__":
app.run(host="0.0.0.0", debug = True)
Conteneuriser ce service¶
installer les dépendances
lancer l’application automatiquement
Les dépendances sont gérées en Python à l’aide d’un fichier requirements.txt qui peut être utilisé pour créer un virtualenv ou un conteneur.
A minima :
flask
redis
(on peut préciser des versions aussi !)
On installe les requirements avec la commande : pip install -r requirements.txt
Ecrire le Dockerfile pour le service Flask en partant de l’image python:3.11-slim
Ajouter le contenu du dossier le contenu du dossier courant au dossier /app du conteneur :
ADD . /app
puis choisir /app comme répertoire de travail
installer les dépendances
exposer le port 5000
lancer run.py
testez !
le docker compose avec le service Redis¶
Ajouter à présent un docker-compose.yml dans votre dossier dont voici les éléments :
Redis étant une image standard on peut directement l’invoquer dans le docker-compose contenant deux services :
redis:
image: "redis"
le service Flask étant décrit par
web:
build: .
ports:
- "4000:5000"
Ecrivez le docker-compose correspondant
puis build et lancement
docker-compose build
docker-compose up -d
Visitez : http://127.0.0.1:4000
Observation de l’app avec les outils Docker¶
docker-compose ps
docker logs ...
Quelles sont les tailles des images utilisées ?
Lister les réseaux ? Les volumes ?
Comment lancer un terminal interactif sur le container Flask ?
Comment arrêter tout ?
Faire le grand ménage ?
Améliorations du Dockerfile¶
On peut définir des variables d’environnement dans FLASK : FLASK_DEBUG à la valeur True si on souhaite être en mode DEBUG et FLASK_APP avec le nom de l’app à lancer.
puis lancer l’app avec la directive : flask run
Effectuez les petites modifs correspondantes. Tester !
Réduction des images¶
essayez les images python:3.11-alpine et python:3.11
comparez les tailles et les temps de build et de lancement
Outils complémentaires pour Docker¶
lazydocker¶

lazydocker est un outil en ligne de commande qui fournit une interface utilisateur pour les conteneurs Docker et Docker Compose. Il fournit une vue d’ensemble des conteneurs en cours d’exécution, des images, des volumes et des réseaux, ainsi que des informations détaillées sur chaque conteneur. L’interface utilisateur est basée sur la bibliothèque de terminal TUI Go mais n’est pas le plus abouti au niveau de l’interface. Il est néanmoins simple d’utilisation et permet de gérer les conteneurs Docker et Docker Compose. Il s’installe via la commande suivante sous Linux :
$ apt install lazydocker
Le site du projet est: https://github.com/jesseduffield/lazydocker
portainer¶

Portainer est un outil web qui permet de gérer les conteneurs Docker et Docker Compose. Il permet de gérer les conteneurs, les images, les volumes, les réseaux, les stacks etc. Il est disponible sous forme d’image Docker et peut donc s’installer via Docker ! On peut par exemple le configurer et le lancer à partir du docker-compose suivant (linux ou mac) :
version: '3'
services:
portainer:
image: portainer/portainer-ce:latest
container_name: portainer
restart: unless-stopped
security_opt:
- no-new-privileges:true
volumes:
- /etc/localtime:/etc/localtime:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./portainer-data:/data
ports:
- 9000:9000
# environment:
# TZ: Europe/Paris
# PORTAINER_DATA: /data
(la partie environment est commentée car elle n’est pas nécessaire sous Unix) On lance la commande suivante pour lancer le service portainer :
$ docker-compose up -d
L’interface web est accessible via l’adresse suivante : http://localhost:9000. Lors du premier lancement, un assistant de configuration est lancé pour créer un compte administrateur, puis choisir l’option « local » pour la connexion à Docker. Il faut nommer le container et puis cliquer sur « connect ». Vous pouvez ensuite visualiser les conteneurs Docker et Docker Compose lancés sur la machine ainsi que leurs environments, volumes, ports, etc. L’interface est très complète et permet de gérer les conteneurs ou des clusters Kubernetes. La documentation est disponible ici : https://docs.portainer.io
yamllint¶
Vérifiez la syntaxe de votre fichier YAML avec la commande suivante :
$ yamllint docker-compose.yml
La doc est ici : https://yamllint.readthedocs.io/en/stable/
autres outils¶
docker desktop : https://www.docker.com/products/docker-desktop (pour Windows et Mac)
docker swarm : https://docs.docker.com/engine/swarm/ (pour gérer des clusters de conteneurs Docker)
hadolint : https://github.com/hadolint/hadolint : Pour vérifier la validité de vos Dockerfile.
dockerize : https://github.com/jwilder/dockerize : Utilitaires pour faciliter la gestion de conteneurs, logs, pour attendre que des services soient disponibles avant de lancer un conteneur.
Scaling docker-compose avec haproxy¶
Mettre en place la structure de projet suivante :
.
├── app
│ ├── Dockerfile
│ ├── requirements.txt
│ ├── run.py
│ └── venv
├── docker-compose.yml
└── haproxy
└── haproxy.cfg
Dans le dossier app :
Un fichier Dockerfile :
FROM python:3.11-alpine
WORKDIR /code
COPY run.py .
COPY requirements.txt .
RUN pip install virtualenv
RUN virtualenv venv
RUN source venv/bin/activate
RUN pip install -r requirements.txt
ENV FLASK_APP=run.py
ENV FLASK_DEBUG=True
EXPOSE 5000
ENTRYPOINT ["python", "run.py"]
Un fichier requirements.txt :
Flask
redis
Le fichier run.py :
from flask import Flask
import redis
import os
import socket
app = Flask(__name__)
redis = redis.Redis(host='redis', port=6379)
@app.route('/')
def hello():
redis.incr('hits')
return 'Hello World! I have been seen {} times.\n'.format(redis.get('hits'))
@app.route('/hostname')
def hostname():
return socket.gethostname()
if __name__ == "__main__":
app.run(host="0.0.0.0", debug = True)
Le fichier haproxy.cfg
Voici l’intégralité du fichier haproxy.cfg à utiliser :
# haproxy config file
# on utilise le dns embarqué de Docker
# la directive server-template web- 4 web:5000 signifie qu'on peut avoir jusqu'à
# 4 containers basés sur le template du service web
defaults
mode http
balance roundrobin
timeout client 60s
timeout connect 60s
timeout server 60s
# cf https://www.haproxy.com/documentation/hapee/latest/configuration/config-sections/resolvers/#
resolvers docker
nameserver dns1 127.0.0.11:53
hold valid 10s
hold other 30s
hold refused 30s
hold nx 30s
hold timeout 30s
hold obsolete 30s
# How many times to retry a query
resolve_retries 3
# How long to wait between retries when no valid response has been received
timeout retry 1s
# How long to wait for a successful resolution
timeout resolve 1s
frontend http
bind *:80
mode http
use_backend all
backend all
mode http
server-template web- 4 web:5000 check resolvers docker init-addr libc,none
listen stats
bind *:80
mode http
stats enable
stats uri /stats
stats refresh 10s
Mettez en place le docker-compose.yml correspondant
Le service « web » :
web:
build: .
ports:
- 5000
deploy:
mode: replicated
replicas: 4
Avec le Dockerfile dans le dossier app :
Le service redis ne change pas et le service lb (load balancing) est assez simple :
lb:
image: haproxy
ports:
- "4000:80"
volumes:
- ./haproxy:/usr/local/etc/haproxy
Quel lien de dépendance faut-il ajouter à ce service ?
Au total le docker-compose.yml devrait ressembler à ceci :
version: '3.9'
services:
web:
build:
context: ./app
dockerfile: Dockerfile
deploy:
mode: replicated
replicas: 4
depends_on:
- redis
redis:
image: "redis:7-alpine"
lb:
image: haproxy
depends_on:
- web
ports:
- "4000:80"
volumes:
- ./haproxy:/usr/local/etc/haproxy
Puis lancement avec scaling :
docker-compose up -d --scale web=4
Testez en consultant: http://127.0.0.1:4000
Que se passe-t-il si on enlève au docker-compose la directive : port : 5000 pour le service web ?
Pour des configurations avec du routage avancé et de l’équilibrage de charge (load balancing), voir les derniers slides du cours et la section sur l’utilisation de Traefik plus loin : traefik.
Aller plus loin avec docker-compose¶
Application Apache PHP Mariadb¶
Architecture du projet¶
.
├── Dockerfile
├── app
│ └── public
│ └── index.php
├── db
├── docker-compose.yml
├── log
└── vhosts
└── 000-default.conf
Le docker-compose.yml¶
Service Web :
web_app:
build: .
container_name: web-app
depends_on:
- db_app
ports:
- "8000:80"
volumes:
- ./log:/var/log/apache2
- ./app:/var/www/html
- ./vhosts:/etc/apache2/sites-enabled
A quoi vont servir les volumes montés ?
Service db :
db_app:
image: mariadb:10.5.8
container_name: data_app
restart: always
environment:
- MYSQL_ROOT_PASSWORD=xxx
- MYSQL_DATABASE=db1
volumes:
- ./db:/var/lib/mysql
ports:
- "3306:3306"
A quoi sert la directive restart: always ?
Quel est le volume monté ? Pourquoi ?
Quel est le port naturel de mariadb ?
Est-ce qu c’est vraiment le bon endroit pour définir le password de root ?
Comment faire autrement ?
Le Dockerfile¶
# On utilise l'image php8.1-apache
FROM php:8.1-apache
# A quoi sert la ligne suivante ?
RUN echo "ServerName localhost" >> /etc/apache2/apache2.conf
# On installe quelques dépendances pour composer et les extensions PHP
RUN apt-get update \
&& apt-get install -y --no-install-recommends locales apt-utils git libicu-dev g++ libpng-dev libxml2-dev libzip-dev libonig-dev libxslt-dev;
# On met en place les locales
RUN echo "en_US.UTF-8 UTF-8" > /etc/locale.gen && \
echo "fr_FR.UTF-8 UTF-8" >> /etc/locale.gen && locale-gen
# On télécharge composer pour gérer les projets PHP
# pourquoi le déplace-t-on ?
RUN curl -sSk https://getcomposer.org/installer | php -- --disable-tls && \
mv composer.phar /usr/local/bin/composer
# on installe et configure les extensions PHP souhaitées
RUN docker-php-ext-configure intl
RUN docker-php-ext-install pdo pdo_mysql gd opcache intl zip calendar dom mbstring zip gd xsl
RUN pecl install apcu && docker-php-ext-enable apcu
# On se place dans le répertoire de publication d'Apache
WORKDIR /var/www/
VOLUME ["/var/www/html","/var/log/apache2","/etc/apache2/sites-enabled"]
Enfin le vhost d’Apache :
<VirtualHost *:80>
ServerName localhost
DocumentRoot /var/www/html/public
DirectoryIndex /index.php
<Directory /var/www/html/public>
AllowOverride None
Order Allow,Deny
Allow from All
FallbackResource /index.php
</Directory>
<Directory /var/www/html/public/bundles>
FallbackResource disabled
</Directory>
ErrorLog /var/log/apache2/project_error.log
CustomLog /var/log/apache2/project_access.log combined
</VirtualHost>
et le fichier index.php
<?php phpinfo(); >
Testez !
docker-compose avancé¶
Architecture du projet¶
├── Readme.md
├── docker
│ ├── db
│ │ └── mariadb
│ │ └── my.cnf
│ └── server
│ ├── Dockerfile
│ ├── apache
│ │ └── sites-enabled
│ │ └── site.conf
│ └── php
│ └── php.ini
├── docker-compose.yml
└── symfony
Fichier .env¶
APP_NAME=symfony_projet
APP_PORT=8100
APP_DB_ADMIN_PORT=8200
DB_PORT=33016
MYSQL_ROOT_PASS=supersecret
MYSQL_USER=app_user
MYSQL_PASS=secretpasswd
MYSQL_DB=symfony_projet
Fichier my.cnf¶
[mysqld]
collation-server = utf8mb4_unicode_ci
character-set-server = utf8mb4
Fichier site.conf¶
<VirtualHost *:80>
DocumentRoot /var/www/html/public
<Directory /var/www/html/public>
AllowOverride None
Order Allow,Deny
Allow from All
<IfModule mod_rewrite.c>
Options -MultiViews
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php [QSA,L]
</IfModule>
</Directory>
</VirtualHost>
fichier Dockerfile dans docker/server¶
FROM php:8.1-apache
RUN a2enmod rewrite
RUN apt-get update && apt-get install -y git unzip zip
WORKDIR /var/www/html/
COPY --from=mlocati/php-extension-installer /usr/bin/install-php-extensions /usr/local/bin/
RUN install-php-extensions gd pdo_mysql bcmath zip intl opcache
COPY --from=composer:latest /usr/bin/composer /usr/local/bin/composer
# met à jour les informations pour git
RUN git config --global user.email "roza@univ-orleans.fr" && git config --global user.name "Gérard Rozsa"
RUN curl -sS https://get.symfony.com/cli/installer | bash \
&& mv /root/.symfony5/bin/symfony /usr/local/bin/symfony
## && symfony new symfony_project --version=stable --dir=/var/www/symfony
Fichier docker-compose.yml¶
version: '3.9'
networks:
symfony_projet_net:
services:
server:
build:
context: .
dockerfile: ./docker/server/Dockerfile
container_name: '${APP_NAME}-server'
ports:
- '${APP_PORT}:80'
working_dir: /var/www/html
environment:
- 'DATABASE_URL=mysql://${MYSQL_USER}:${MYSQL_PASS}@db_server:3306/${MYSQL_DB}?serverVersion=10.9'
volumes:
- ./symfony:/var/www/html
- ./docker/server/apache/sites-enabled:/etc/apache2/sites-enabled
- ./docker/server/php/php.ini:/usr/local/etc/php/conf.d/extra-php-config.ini
depends_on:
db_server:
condition: service_healthy
networks:
- symfony_projet_net
db_server:
image: mariadb:10.9.3
container_name: '${APP_NAME}-db'
restart: always
ports:
- '${DB_PORT}:3306'
environment:
MYSQL_ROOT_PASSWORD: '${MYSQL_ROOT_PASS}'
MYSQL_USER: '${MYSQL_USER}'
MYSQL_PASSWORD: '${MYSQL_PASS}'
MYSQL_DATABASE: '${MYSQL_DB}'
volumes:
- db_data:/var/lib/mysql
- ./docker/db/mariadb/my.cnf:/etc/mysql/conf.d/my.cnf
healthcheck:
test: mysqladmin ping -h 127.0.0.1 -u root --password=$$MYSQL_ROOT_PASSWORD
interval: 5s
retries: 5
networks:
- symfony_projet_net
db_admin:
image: phpmyadmin/phpmyadmin:5
container_name: '${APP_NAME}-db-admin'
ports:
- '${APP_DB_ADMIN_PORT}:80'
environment:
PMA_HOST: db_server
depends_on:
db_server:
condition: service_healthy
volumes:
- db_admin_data:/var/www/html
networks:
- symfony_projet_net
volumes:
db_data:
db_admin_data:
Observations/Questions¶
1. Observez l’usage des variables d’environnement dans .env. Ces variables peuvent ensuite être réutilisées dans les fichiers Docker comme dans docker-compose.yml : DATABASE_URL=mysql://${MYSQL_USER}:${MYSQL_PASS}@db_server:3306/${MYSQL_DB}?serverVersion=10.5`
Observez les services. Combien y en a-t-il ?
Observez le Dockerfile. Quelle est son utilité ? Ses différentes étapes ? Expliquez le fonctionnement des commandes COPY utilisées.
Quels sont les volumes montés et leurs rôles ?
Tester le service phpmyadmin.
Sur quel port tourne-t-il ?
Pourrait-on mettre ce numéro de port en paramètre ?
Quels sont les identifiants pour se connecter ?
Pour le service correspondant à Apache/Symfony :
ouvrez un terminal sur le conteneur concerné : docker exec -it symfony_projet-server bash
placez-vous dans /var/www/html
Lancez la commande : composer create-project symfony/website-skeleton .
Qu’observez-vous dans votre dossier de travail ?
Testez si Symfony est bien accessible
Installer Wordpress avec docker-compose¶
Il suffit d’un docker-compose avec 3 services :
un service mariadb
un service wordpress
un service phpmyadmin
Le fichier docker-compose.yml¶
version: '3.3'
services:
db:
image: mariadb:10.9.3
volumes:
- db_data:/var/lib/mysql
restart: always
environment:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: wordpress
phpmyadmin:
depends_on:
- db
image: phpmyadmin/phpmyadmin
restart: always
ports:
- '8080:80'
environment:
PMA_HOST: db
MYSQL_ROOT_PASSWORD: password
wordpress:
depends_on:
- db
image: wordpress:latest
ports:
- '8000:80'
restart: always
volumes: ['./wp:/var/www/html']
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: wordpress
volumes:
db_data:
On lance avec la commande :
docker-compose up -d
On peut vérifier que tout est bien lancé avec la commande :
docker-compose ps
Puis on visite l’adresse http://localhost:8000 pour voir le site wordpress qui présente un petit assistant d’installation.
Déploiement sur Gitpod.io¶
Gitpod.io permet de déployer une app hébergée sur github ou gitlab dans un environnement de développement en ligne containeurisé.
Exemples :
Pour déployer une app sur gitpod, il suffit de créer un fichier .gitpod.yml à la racine du projet qui décrit les étapes de créations de l’environnement de développement, un peu à la manière d’un Dockerfile.
Intégration continue sur Gitlab¶
On peut définir un ou plusieurs pipelines dans le fichier .gitlab-ci.yml qui décrit les étapes de création de l’environnement d’intégration continue, à la manière d’un docker-compose.
Voir :
Tests et Gitlab CI (Intégration continue)
pour en savoir plus !