Module 9 · 9.3
Français
Docker compose et le pare-feu
Module 9: Docker et docker compose · Temps de lecture : 12 minutes
Objectif pédagogique : À l'issue de cette leçon, vous saurez décrire des services dans un fichier compose, les démarrer, les mettre à jour et les arrêter, et vous pourrez expliquer pourquoi les ports publiés par Docker contournent ufw et comment l'éviter.
Notions clés : compose.yaml, services, image, ports, volumes, environment, .env, restart, networks, depends_on, healthcheck, docker compose up -d, pull, logs, down, version: obsolète, Docker et ufw, DOCKER-USER, publication sur 127.0.0.1, secrets, docker.sock
Pourquoi compose ?
Avec docker run (leçon 9.2), vous démarrez un seul conteneur avec une longue ligne d'options. Une vraie application se compose souvent de plusieurs conteneurs : un programme, une base de données, un reverse proxy. Retenir ces lignes et les retaper après un redémarrage ou une mise à jour finit tôt ou tard par mal tourner.
Docker Compose décrit tous les conteneurs d'une application dans un seul fichier : compose.yaml. Vous placez ce fichier dans un dossier dédié, par exemple /srv/status/compose.yaml, et vous gérez tout avec docker compose. Le fichier sert en même temps de documentation : ce qui tourne, quelle image, quels ports, où se trouvent les données. Incluez ce dossier dans votre sauvegarde (leçon 8.1), et vous pourrez restaurer l'application sur un nouveau serveur en quelques minutes.
Le plug-in s'appelle docker compose (avec une espace, version 2). L'ancien programme séparé docker-compose (avec un trait d'union) a été remplacé ; vous le rencontrerez encore dans d'anciens tutoriels. Le champ version: "3.8" en haut des anciens fichiers est lui aussi obsolète : Compose utilise toujours le schéma le plus récent et se contente d'afficher un avertissement en présence de version:.
Lire un fichier compose
Un exemple avec un programme et une base de données PostgreSQL :
# /srv/notities/compose.yaml
services:
app:
image: ghcr.io/example/notities:1.4
restart: unless-stopped
ports:
- "127.0.0.1:8081:8080"
environment:
DB_HOST: db
DB_USER: notities
DB_PASSWORD: ${DB_PASSWORD}
depends_on:
db:
condition: service_healthy
db:
image: postgres:17
restart: unless-stopped
environment:
POSTGRES_USER: notities
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U notities"]
interval: 10s
retries: 5
volumes:
db-data:Étape par étape :
- services : chaque service devient un conteneur. Le nom (
app,db) est aussi le nom d'hôte grâce auquel les conteneurs se trouvent entre eux. Compose crée un réseau propre à l'application, doncappse connecte à la base de données surdb:5432. - image : toujours avec un tag de version (leçon 9.1).
- restart : ce que fait Docker lorsque le conteneur s'arrête ou que le serveur redémarre.
unless-stoppedle relance, sauf si vous l'avez arrêté volontairement. Sans politique de redémarrage, votre application ne revient pas après un redémarrage (leçon 4.2). - ports : seule l'application publie un port, et uniquement sur
127.0.0.1. La base de données ne publie rien : l'application l'atteint via le réseau interne. Une base de données avecports: - "5432:5432"est l'une des erreurs les plus fréquentes (voir plus bas). - environment : les paramètres du programme. Le mot de passe n'y figure pas en clair, mais sous la forme
${DB_PASSWORD}. - volumes : le volume nommé
db-dataconserve la base de données lorsque le conteneur est remplacé. - depends_on avec healthcheck : l'application ne démarre que lorsque la base de données est réellement prête, pas seulement démarrée.
Les secrets dans un fichier .env
Compose lit les variables comme ${DB_PASSWORD} dans un fichier .env situé dans le même dossier :
# /srv/notities/.env
DB_PASSWORD=Kies-hier-een-lang-willekeurig-wachtwoordanna@web1:/srv/notities$ sudo chmod 600 .envAinsi, le mot de passe ne figure pas dans le fichier compose que vous conservez peut-être dans Git ou que vous collez sur un forum pour demander de l'aide. Ajoutez .env à .gitignore. Générez le mot de passe avec un gestionnaire de mots de passe ou avec openssl rand -base64 24.
Utilisation au quotidien
Toutes les commandes s'exécutent dans le dossier qui contient le fichier compose :
anna@web1:/srv/notities$ sudo docker compose config # vérifier le fichier, variables remplacées
anna@web1:/srv/notities$ sudo docker compose up -d # tout démarrer (ou mettre à jour selon le fichier)
anna@web1:/srv/notities$ sudo docker compose ps # état par service
anna@web1:/srv/notities$ sudo docker compose logs -f app
anna@web1:/srv/notities$ sudo docker compose pull # récupérer les nouvelles images
anna@web1:/srv/notities$ sudo docker compose up -d # remplacer les conteneurs par les nouvelles images
anna@web1:/srv/notities$ sudo docker compose down # arrêter et supprimer les conteneursdown conserve vos volumes. down -v supprime aussi les volumes de l'application, et donc votre base de données. Ne tapez cette commande que si vous voulez vraiment repartir de zéro.
docker compose config est l'équivalent de nginx -t et sshd -t : elle montre comment Compose interprète votre fichier, avec toutes les variables remplacées, avant que vous ne démarriez quoi que ce soit. Veillez à ne pas coller cette sortie n'importe où, car les mots de passe y apparaissent.
Docker contourne ufw
Voici le point de sécurité le plus important de ce module. Dans la leçon 5.2, vous avez configuré ufw sur « tout refuser, sauf SSH, 80 et 443 ». Vous pourriez donc penser qu'un conteneur avec ports: - "5432:5432" n'est pas accessible. Ce n'est pas le cas.
La documentation de Docker le dit explicitement : Docker et ufw utilisent les règles de pare-feu d'une manière incompatible. Lorsque vous publiez un port, Docker ajoute lui-même des règles dans le pare-feu du noyau. Ces règles redirigent le trafic vers le conteneur (traduction d'adresse, DNAT) avant que les règles d'ufw ne s'appliquent. Le paquet contourne donc ufw. ufw status n'affiche rien de particulier, alors que le port est accessible depuis tout Internet. Il en va de même pour firewalld.
De plus, sans adresse précisée, Docker publie sur 0.0.0.0 et [::], donc en IPv4 et en IPv6 (leçon 9.1).
Trois façons de bien faire, de la plus simple à la plus complexe :
- Ne publiez rien qui n'ait pas besoin d'être public. Les bases de données, les caches et les services internes ne reçoivent pas de
ports:. Les conteneurs d'une même application compose se joignent via le réseau interne. - Publiez sur l'interface loopback.
"127.0.0.1:8081:8080". Seul le serveur lui-même peut alors y accéder, et vous placez devant un reverse proxy (nginx ou Caddy, module 7) qui écoute sur le port 443. Vous pouvez aussi en faire le comportement par défaut de Docker : un paramètre du démon fait atterrir sur127.0.0.1tous les ports publiés sans adresse. - Des règles dans la chaîne DOCKER-USER. Docker crée une chaîne vide
DOCKER-USER, parcourue avant ses propres règles. Vous pouvez y ajouter vos propres règles, par exemple « seule cette adresse IP peut accéder aux ports publiés ». C'est un travail avancé avec iptables ou nftables, où l'erreur est facile. Pour la plupart des petits serveurs, les options 1 et 2 suffisent.
Vérifiez toujours depuis l'extérieur, comme dans la leçon 5.1 : nc -vz -w 3 <ip> 5432 depuis votre ordinateur portable, ou la vérification de la formation des exercices Z5.1 et Z10.4.
Deux autres choses à ne pas faire
- Ne montez pas
/var/run/docker.sockdans un conteneur, sauf si vous savez exactement pourquoi. Un conteneur ayant accès au socket Docker peut lui-même démarrer des conteneurs, y compris un conteneur avec tout le disque de l'hôte (leçon 9.2). Certains outils (tableaux de bord, outils de mise à jour automatique) le demandent ; cela revient en pratique à donner les droits root à ce programme. - N'utilisez pas
privileged: trueounetwork_mode: hostsimplement parce qu'un tutoriel le dit. Les deux suppriment en grande partie l'isolation du conteneur. Ne les utilisez que si l'éditeur explique pourquoi c'est nécessaire.
Résumé
- Un fichier compose (
compose.yaml) décrit tous les conteneurs d'une application ; il sert aussi de documentation et doit figurer dans votre sauvegarde. - Utilisez des tags de version, une politique de redémarrage, des volumes nommés, un healthcheck lorsque c'est possible, et placez les secrets dans un
.envavec les droits 600. docker compose config,up -d,ps,logs,pull,down;down -vsupprime aussi les volumes.- Les ports publiés contournent ufw et firewalld. Ne publiez que ce qui doit être public, et sinon sur
127.0.0.1avec un reverse proxy devant. - Pas de docker.sock, de
privilegedni denetwork_mode: hostsans bonne raison.
Quiz
Exercice
Exercice Z9.3: Vérifier un fichier compose. Le fichier d'exemple compose-nakijken.txt contient (sous forme de fichier texte) le fichier compose d'une petite boutique en ligne avec une application, une base de données, un cache et un outil d'administration. Il comporte six erreurs abordées dans cette leçon ou dans les leçons 9.1 et 9.2. Indiquez pour chaque bloc de lignes numéroté s'il est correct ou erroné, et choisissez pour chaque erreur la bonne correction. Le programme compare vos réponses à la clé de correction. Si vous avez votre propre fichier compose, passez-le ensuite en revue selon les mêmes points.
Pour aller plus loin
- Documentation Docker, How Compose works (compose.yaml) : https://docs.docker.com/compose/intro/compose-application-model/
- Documentation Docker, Version and name top-level elements : https://docs.docker.com/reference/compose-file/version-and-name/
- Documentation Docker, Packet filtering and firewalls (Docker et ufw) : https://docs.docker.com/engine/network/packet-filtering-firewalls/
- Documentation Docker, Port publishing and mapping : https://docs.docker.com/engine/network/port-publishing/
- Documentation Docker, Interpolation (variables et .env) : https://docs.docker.com/reference/compose-file/interpolation/
