4. TP Docker — Écrire son premier Dockerfile 🐍⚓︎
Projet : application Flask + Redis sur srv-debian
🎯 Objectifs du TP
- Comprendre le rôle d'un Dockerfile
- Écrire un Dockerfile pour une application Python
- Écrire un docker-compose.yml de zéro
- Faire communiquer 2 conteneurs via un réseau Docker
- Corriger ses erreurs en lisant les logs
Prérequis
- Avoir réalisé le TP précédent (WordPress avec Docker Compose)
- Être connecté à
srv-debianen SSH
🗺️ Ce qu'on va construire⚓︎
Une application web Python qui compte le nombre de visites du site.
À chaque fois qu'un visiteur charge la page, le compteur s'incrémente.
Le compteur est stocké dans Redis (une base de données ultra-rapide en mémoire).
Navigateur → [Flask :5000] → [Redis :6379]
conteneur conteneur
(Python) (compteur)
Ce qui est fourni : le code Python (app.py + requirements.txt)
Ce que vous écrivez : le Dockerfile et le docker-compose.yml
Étape 1 — Préparer son espace de travail 📁⚓︎
Connectez-vous à srv-debian et créez un dossier pour ce TP :
ssh VOTRE_PRENOM@srv-debian.local
mkdir ~/flask-counter
cd ~/flask-counter
Étape 2 — Découvrir le code fourni 🐍⚓︎
Le fichier app.py⚓︎
Créez le fichier app.py avec le contenu suivant :
nano app.py
from flask import Flask
from redis import Redis
app = Flask(__name__)
redis = Redis(host='redis', port=6379) # 'redis' = nom du service Docker
@app.route('/')
def index():
count = redis.incr('visits')
return f'''
<html>
<body style="font-family: Arial; text-align: center; margin-top: 100px;">
<h1>🐍 Flask + Docker</h1>
<h2>Cette page a été visitée <strong>{count}</strong> fois.</h2>
<p><em>Actualisez la page pour incrémenter le compteur !</em></p>
</body>
</html>
'''
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=True)
Sauvegardez : Ctrl+O → Entrée → Ctrl+X
🔍 Lecture du code — pas de panique !
Vous n'avez pas besoin de connaître Python pour ce TP.
Retenez juste deux lignes importantes :
Redis(host='redis', ...)→ Flask contacte Redis en utilisant le nom du service Dockerredisapp.run(host='0.0.0.0', port=5000)→ l'application écoute sur le port 5000
Ce sont exactement les informations dont vous aurez besoin pour écrire le Dockerfile et le docker-compose.yml.
Le fichier requirements.txt⚓︎
Ce fichier liste les dépendances Python à installer (l'équivalent du composer.json de Laravel).
nano requirements.txt
flask
redis
Sauvegardez et quittez.
Vérifier la structure⚓︎
ls -la
Vous devez avoir :
-rw-r--r-- app.py
-rw-r--r-- requirements.txt
Étape 3 — Écrire le Dockerfile 📝⚓︎
Rappel — À quoi sert le Dockerfile ?
Le Dockerfile décrit comment construire l'image de votre application.
C'est la recette : quel OS de base, quelles dépendances installer, quel code copier, quelle commande lancer.
Créez le fichier Dockerfile :
nano Dockerfile
🧩 Construction guidée⚓︎
Complétez le Dockerfile instruction par instruction en vous aidant des indices ci-dessous.
Instruction 1 — Image de base
On part d'une image Python officielle. La version utilisée est 3.11-slim
(slim = version allégée, sans les outils de développement inutiles en production).
FROM ______:______
Indice
FROM python:3.11-slim
Instruction 2 — Dossier de travail
Définissez /app comme dossier de travail dans le conteneur.
Toutes les commandes suivantes s'exécuteront depuis ce dossier.
WORKDIR ______
Indice
WORKDIR /app
Instruction 3 — Copier les dépendances EN PREMIER
Bonne pratique — optimisation du cache Docker
On copie requirements.txt avant le reste du code.
Pourquoi ? Si on copie tout d'abord (COPY . .), Docker recalcule
l'installation des dépendances à chaque modification du code — même si
requirements.txt n'a pas changé. En le copiant seul en premier,
Docker met en cache la couche d'installation tant que le fichier ne change pas.
COPY ______ .
Indice
COPY requirements.txt .
Instruction 4 — Installer les dépendances Python
pip est le gestionnaire de paquets Python (l'équivalent de composer en PHP).
--no-cache-dir évite de stocker le cache pip dans l'image (inutile, fait grossir l'image).
RUN pip install --no-cache-dir -r ______
Indice
RUN pip install --no-cache-dir -r requirements.txt
Instruction 5 — Copier le reste du code
Maintenant qu'on a installé les dépendances, on copie le code source.
COPY ______ ______
Indice
COPY . .
Instruction 6 — Exposer le port
L'application Flask écoute sur quel port ? (regardez app.py)
EXPOSE ______
Indice
EXPOSE 5000
Instruction 7 — Commande de démarrage
La commande pour lancer l'application Python est python app.py.
CMD ["______", "______"]
Indice
CMD ["python", "app.py"]
✅ Dockerfile complet attendu⚓︎
Une fois toutes les instructions complétées, votre Dockerfile doit ressembler à ceci.
Ne regardez qu'après avoir essayé !
Solution complète
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 5000
CMD ["python", "app.py"]
Étape 4 — Écrire le docker-compose.yml ⚙️⚓︎
Rappel — À quoi sert docker-compose.yml ?
Il orchestre plusieurs conteneurs ensemble : il définit les services,
les ports, les réseaux et les volumes. C'est lui qui fait que Flask
peut parler à Redis en utilisant simplement le nom redis.
Créez le fichier :
nano docker-compose.yml
🧩 Construction guidée⚓︎
Structure de base
services:
web:
# ... service Flask
redis:
# ... service Redis
networks:
# ... réseau interne
Service web — l'application Flask
Flask n'utilise pas une image toute faite — elle se construit à partir de votre Dockerfile.
Le mot-clé pour ça est build: . (construire depuis le dossier courant).
web:
build: ______
container_name: flask_VOTRE_PRENOM
restart: unless-stopped
ports:
- "______ :5000" # PORT_EXTERNE:PORT_INTERNE
networks:
- ______
depends_on:
- ______
Quel port externe choisir ?
Vous avez déjà le port 808X occupé par WordPress.
Choisissez un port dans la plage 9081-9083 selon votre prénom :
| Étudiant | Port WordPress | Port Flask |
|---|---|---|
| elouan | 8081 | 9081 |
| alexandre | 8082 | 9082 |
| mael | 8083 | 9083 |
Indice service web
web:
build: .
container_name: flask_elouan
restart: unless-stopped
ports:
- "9081:5000"
networks:
- flask_network
depends_on:
- redis
Service redis — la base de données
Redis est une image officielle disponible sur Docker Hub.
Elle n'a pas besoin de configuration particulière.
redis:
image: ______:______
container_name: redis_VOTRE_PRENOM
restart: unless-stopped
networks:
- ______
Quelle image Redis utiliser ?
redis:alpine — la version légère (alpine = distribution Linux minimaliste).
C'est l'image officielle recommandée pour la production.
Indice service redis
redis:
image: redis:alpine
container_name: redis_elouan
restart: unless-stopped
networks:
- flask_network
Le réseau interne
Déclarez le réseau qui permet à Flask et Redis de communiquer.
networks:
flask_network:
driver: ______
Indice réseau
networks:
flask_network:
driver: bridge
✅ docker-compose.yml complet attendu⚓︎
Solution complète (elouan)
services:
web:
build: .
container_name: flask_elouan
restart: unless-stopped
ports:
- "9081:5000"
networks:
- flask_network
depends_on:
- redis
redis:
image: redis:alpine
container_name: redis_elouan
restart: unless-stopped
networks:
- flask_network
networks:
flask_network:
driver: bridge
Étape 5 — Construire et tester 🚀⚓︎
Vérifier la structure des fichiers⚓︎
ls -la ~/flask-counter/
Vous devez avoir 4 fichiers :
app.py
requirements.txt
Dockerfile
docker-compose.yml
Lancer le build et démarrer⚓︎
docker compose up -d --build
Que se passe-t-il ?
--build: Docker construit l'image à partir de votre Dockerfile-d: démarre les conteneurs en arrière-plan
La première fois, Docker télécharge python:3.11-slim et redis:alpine.
C'est normal que ça prenne 1 à 2 minutes.
Vérifier l'état⚓︎
docker compose ps
Résultat attendu :
NAME STATUS PORTS
flask_elouan running 0.0.0.0:9081->5000/tcp
redis_elouan running
Étape 6 — Tester dans le navigateur 🌐⚓︎
| Étudiant | URL à ouvrir |
|---|---|
| elouan | http://elouan.srv-debian.local:9081 |
| alexandre | http://alexandre.srv-debian.local:9082 |
| mael | http://mael.srv-debian.local:9083 |
Actualisez la page plusieurs fois — le compteur doit s'incrémenter à chaque fois.
🎉 Ça fonctionne !
Votre application Flask communique avec Redis via le réseau Docker interne.
Flask utilise le nom redis pour joindre le conteneur Redis — c'est le DNS interne Docker.
Étape 7 — Corriger ses erreurs 🔧⚓︎
C'est la compétence la plus importante en DevOps : savoir diagnostiquer.
Les erreurs les plus fréquentes⚓︎
La page ne s'affiche pas → vérifier les logs
docker compose logs web
docker compose logs redis
Erreur dans le Dockerfile → reconstruire
# Après correction du Dockerfile
docker compose up -d --build
Un conteneur redémarre en boucle
docker compose ps
# STATUS = "restarting" → problème de configuration
docker compose logs web --tail=20
Tester la connexion entre Flask et Redis
# Entrer dans le conteneur Flask
docker compose exec web bash
# Tester la connexion réseau vers Redis
python3 -c "import redis; r=redis.Redis(host='redis'); print(r.ping())"
# Résultat attendu : True
exit
Étape 8 — Questions de réflexion 🧠⚓︎
📝 À répondre dans votre compte-rendu
Question 1
Dans app.py, la connexion Redis utilise host='redis'.
Pourquoi ce nom fonctionne-t-il ? Quel mécanisme Docker est derrière ?
Question 2
Dans votre docker-compose.yml, vous avez défini depends_on: redis.
Que se passerait-il si vous supprimiez cette ligne et que Redis mettait
10 secondes à démarrer ?
Question 3
Comparez les deux docker-compose.yml : celui du TP WordPress et celui que
vous venez d'écrire. Quelle est la différence entre image: et build: ?
Dans quel cas utilise-t-on chacun ?
Question 4
Le compteur de visites est stocké dans Redis, pas sur disque.
Que se passe-t-il si vous faites docker compose down puis docker compose up -d ?
Testez et expliquez. Comment corriger ça ?
Éléments de correction
Q1 — Docker crée un DNS interne dans chaque réseau. Le nom du service
(redis) est automatiquement résolu vers l'IP du conteneur Redis.
C'est pour ça que dans le .env Laravel on met DB_HOST=mysql au lieu de localhost.
Q2 — Flask démarrerait avant Redis et tenterait de s'y connecter → erreur de
connexion → le conteneur Flask crasherait. depends_on garantit l'ordre de démarrage.
(Note avancée : depends_on attend que le conteneur soit started, pas que Redis
soit réellement prêt à accepter des connexions → pour ça il faudrait un healthcheck.)
Q3 — image: utilise une image déjà construite (depuis Docker Hub ou en local).
build: construit une nouvelle image depuis un Dockerfile. On utilise build:
quand on a du code applicatif à embarquer dans l'image.
Q4 — Le compteur repart à zéro car Redis stocke les données en mémoire,
dans le conteneur. Sans volume, tout disparaît à docker compose down.
Pour corriger, ajouter un volume au service Redis :
redis:
image: redis:alpine
volumes:
- redis_data:/data
command: redis-server --appendonly yes
volumes:
redis_data:
🧾 Synthèse — Ce que vous avez écrit⚓︎
| Fichier | Votre rôle |
|---|---|
app.py |
Fourni — code de l'application Flask |
requirements.txt |
Fourni — dépendances Python |
Dockerfile |
✍️ Écrit par vous — recette de l'image |
docker-compose.yml |
✍️ Écrit par vous — orchestration 2 services |
🚀 Bonus — Aller plus loin⚓︎
Bonus 1 — Ajouter la persistance Redis
Modifiez votre docker-compose.yml pour que le compteur survive à un
docker compose down. Indice : volumes + redis-server --appendonly yes.
Bonus 2 — Modifier l'application
Éditez app.py pour afficher en plus :
- La date et heure de la dernière visite
- Un bouton Reset qui remet le compteur à zéro
Après modification, reconstruisez l'image :
docker compose up -d --build
Bonus 3 — Explorer Redis en ligne de commande
docker compose exec redis redis-cli
127.0.0.1:6379> GET visits
127.0.0.1:6379> SET visits 0
127.0.0.1:6379> KEYS *
127.0.0.1:6379> EXIT
visits à 0 — que constatez-vous ?