Exercices et Projets Docker
Objectifs du chapitre
- Consolider les connaissances acquises
- Pratiquer avec des exercices progressifs
- Réaliser des projets complets
- Se préparer aux cas réels
1 - Exercices de base
Exercice 1 : Premier conteneur
Objectif : Lancer et manipuler un conteneur nginx
# 1. Télécharger l'image nginx
docker pull nginx:alpine
# 2. Lancer un conteneur nommé "web" sur le port 8080
docker run -d --name web -p 8080:80 nginx:alpine
# 3. Vérifier que le conteneur fonctionne
curl http://localhost:8080
# 4. Voir les logs du conteneur
docker logs web
# 5. Arrêter et supprimer le conteneur
docker stop web && docker rm web
Questions :
- Quelle est la différence entre
docker stopetdocker kill? - Que se passe-t-il si vous essayez de supprimer un conteneur en cours d'exécution ?
Exercice 2 : Variables d'environnement
Objectif : Configurer une base de données MySQL avec des variables d'environnement
# 1. Lancer MySQL avec configuration personnalisée
docker run -d \
--name mysql-db \
-e MYSQL_ROOT_PASSWORD=rootpass \
-e MYSQL_DATABASE=myapp \
-e MYSQL_USER=appuser \
-e MYSQL_PASSWORD=apppass \
-p 3306:3306 \
mysql:8
# 2. Vérifier que la base de données est prête
docker logs mysql-db
# 3. Se connecter à MySQL
docker exec -it mysql-db mysql -u appuser -papppass myapp
# 4. Dans MySQL, créer une table
CREATE TABLE users (id INT PRIMARY KEY, name VARCHAR(100));
INSERT INTO users VALUES (1, 'Alice');
SELECT * FROM users;
EXIT;
Exercice 3 : Volumes
Objectif : Persister les données d'une base PostgreSQL
# 1. Créer un volume nommé
docker volume create postgres-data
# 2. Lancer PostgreSQL avec le volume
docker run -d \
--name postgres \
-e POSTGRES_PASSWORD=secret \
-v postgres-data:/var/lib/postgresql/data \
postgres:15
# 3. Créer des données
docker exec -it postgres psql -U postgres -c "CREATE TABLE test (id serial, name text);"
docker exec -it postgres psql -U postgres -c "INSERT INTO test (name) VALUES ('Hello');"
# 4. Supprimer le conteneur
docker rm -f postgres
# 5. Recréer le conteneur avec le même volume
docker run -d \
--name postgres \
-e POSTGRES_PASSWORD=secret \
-v postgres-data:/var/lib/postgresql/data \
postgres:15
# 6. Vérifier que les données sont préservées
docker exec -it postgres psql -U postgres -c "SELECT * FROM test;"
2 - Exercices intermédiaires
Exercice 4 : Créer un Dockerfile
Objectif : Conteneuriser une application Python Flask
Structure du projet :
flask-app/
├── app.py
├── requirements.txt
└── Dockerfile
app.py :
from flask import Flask
import os
app = Flask(__name__)
@app.route('/')
def hello():
name = os.environ.get('APP_NAME', 'World')
return f'Hello, {name}!'
@app.route('/health')
def health():
return {'status': 'healthy'}
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
requirements.txt :
flask==3.0.0
gunicorn==21.2.0
Dockerfile (à compléter) :
# Utiliser Python 3.11 slim
FROM python:3.11-slim
# Définir le répertoire de travail
WORKDIR /app
# Copier et installer les dépendances
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copier le code
COPY app.py .
# Créer un utilisateur non-root
RUN useradd -m appuser
USER appuser
# Exposer le port
EXPOSE 5000
# Healthcheck
HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:5000/health')"
# Commande de démarrage
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "app:app"]
Commandes :
# Build
docker build -t flask-app:v1 .
# Run
docker run -d -p 5000:5000 -e APP_NAME=Docker flask-app:v1
# Test
curl http://localhost:5000
Exercice 5 : Réseaux Docker
Objectif : Faire communiquer une application web avec une base de données
# 1. Créer un réseau
docker network create app-network
# 2. Lancer la base de données
docker run -d \
--name db \
--network app-network \
-e POSTGRES_PASSWORD=secret \
-e POSTGRES_DB=myapp \
postgres:15
# 3. Tester la résolution DNS depuis un autre conteneur
docker run --rm --network app-network alpine ping -c 3 db
# 4. Lancer une application qui se connecte à la base
docker run -d \
--name app \
--network app-network \
-p 8080:8080 \
-e DATABASE_URL=postgres://postgres:secret@db:5432/myapp \
adminer
# 5. Accéder à Adminer via http://localhost:8080
Exercice 6 : Multi-stage build
Objectif : Optimiser une image Node.js avec un multi-stage build
Application :
// index.js
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.json({ message: 'Hello from Docker!' });
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
package.json :
{
"name": "node-app",
"version": "1.0.0",
"main": "index.js",
"dependencies": {
"express": "^4.18.2"
}
}
Dockerfile multi-stage :
# Stage 1: Dependencies
FROM node:18 AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
# Stage 2: Production
FROM node:18-alpine
WORKDIR /app
RUN addgroup -g 1001 -S nodejs && adduser -S -u 1001 -G nodejs nodejs
COPY --from=deps /app/node_modules ./node_modules
COPY --chown=nodejs:nodejs . .
USER nodejs
EXPOSE 3000
CMD ["node", "index.js"]
Comparer les tailles :
# Build sans multi-stage
# FROM node:18
# WORKDIR /app
# COPY . .
# RUN npm install
# CMD ["node", "index.js"]
docker build -t node-app:basic -f Dockerfile.basic .
docker build -t node-app:optimized .
docker images | grep node-app
# node-app basic ~1GB
# node-app optimized ~150MB
3 - Projets complets
Projet 1 : Application MERN Stack
Architecture :
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Frontend │────▶│ Backend │────▶│ MongoDB │
│ (React) │ │ (Express) │ │ │
│ :3000 │ │ :5000 │ │ :27017 │
└─────────────┘ └─────────────┘ └──── ─────────┘
docker-compose.yml :
version: "3.9"
services:
frontend:
build: ./frontend
ports:
- "3000:3000"
environment:
- REACT_APP_API_URL=http://localhost:5000
depends_on:
- backend
backend:
build: ./backend
ports:
- "5000:5000"
environment:
- MONGODB_URI=mongodb://mongo:27017/mernapp
- JWT_SECRET=supersecretkey
depends_on:
- mongo
mongo:
image: mongo:7
volumes:
- mongo-data:/data/db
environment:
- MONGO_INITDB_DATABASE=mernapp
volumes:
mongo-data:
Projet 2 : Stack de monitoring
Objectif : Déployer Prometheus + Grafana pour le monitoring
docker-compose.yml :
version: "3.9"
services:
prometheus:
image: prom/prometheus:v2.47.0
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus-data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
grafana:
image: grafana/grafana:10.1.0
ports:
- "3000:3000"
volumes:
- grafana-data:/var/lib/grafana
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
depends_on:
- prometheus
node-exporter:
image: prom/node-exporter:v1.6.1
ports:
- "9100:9100"
volumes:
prometheus-data:
grafana-data:
prometheus.yml :
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
- job_name: 'node'
static_configs:
- targets: ['node-exporter:9100']
Projet 3 : Application WordPress haute disponibilité
docker-compose.yml :
version: "3.9"
services:
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- wordpress1
- wordpress2
wordpress1:
image: wordpress:6-fpm-alpine
environment:
WORDPRESS_DB_HOST: mariadb
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: wordpress
WORDPRESS_DB_NAME: wordpress
volumes:
- wp-content:/var/www/html/wp-content
depends_on:
- mariadb
- redis
wordpress2:
image: wordpress:6-fpm-alpine
environment:
WORDPRESS_DB_HOST: mariadb
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: wordpress
WORDPRESS_DB_NAME: wordpress
volumes:
- wp-content:/var/www/html/wp-content
depends_on:
- mariadb
- redis
mariadb:
image: mariadb:11
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: wordpress
volumes:
- db-data:/var/lib/mysql
redis:
image: redis:7-alpine
volumes:
wp-content:
db-data:
4 - Défis avancés
Défi 1 : Optimisation extrême
Réduisez la taille de cette image au maximum :
# Image de départ : ~1.2 GB
FROM node:18
WORKDIR /app
COPY . .
RUN npm install
CMD ["npm", "start"]
Objectif : Atteindre moins de 100 MB
Indices :
- Multi-stage build
- Image Alpine ou distroless
- Production dependencies only
- Nettoyage du cache npm
Défi 2 : Sécurisation complète
Sécurisez cette configuration :
# Configuration NON sécurisée
services:
app:
image: myapp
ports:
- "3000:3000"
environment:
- DB_PASSWORD=secret123
db:
image: postgres
environment:
- POSTGRES_PASSWORD=root
Points à améliorer :
- Secrets
- Utilisateur non-root
- Réseau isolé
- Healthchecks
- Limites de ressources
- Images versionnées
Défi 3 : CI/CD Pipeline
Créez un pipeline GitHub Actions qui :
- Build l'image Docker
- Exécute les tests
- Scanne les vulnérabilités
- Pousse vers Docker Hub
- Déploie (optionnel)
# .github/workflows/docker.yml
name: Docker CI/CD
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build image
run: docker build -t myapp:${{ github.sha }} .
- name: Run tests
run: docker run myapp:${{ github.sha }} npm test
- name: Scan vulnerabilities
uses: aquasecurity/trivy-action@master
with:
image-ref: myapp:${{ github.sha }}
- name: Push to Docker Hub
if: github.ref == 'refs/heads/main'
run: |
echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
docker tag myapp:${{ github.sha }} ${{ secrets.DOCKER_USERNAME }}/myapp:latest
docker push ${{ secrets.DOCKER_USERNAME }}/myapp:latest
5 - Quiz de révision
Questions
-
Quelle est la différence entre
CMDetENTRYPOINT? -
Pourquoi utiliser un multi-stage build ?
-
Quelle commande permet de voir les logs d'un conteneur en temps réel ?
-
Comment persister les données d'une base de données ?
-
Quelle est la différence entre un volume et un bind mount ?
-
Comment les conteneurs communiquent-ils sur un réseau Docker personnalisé ?
-
Pourquoi ne pas exécuter un conteneur en tant que root ?
-
Comment limiter la mémoire d'un conteneur à 512 MB ?
Réponses
Voir les réponses
-
CMDdéfinit la commande par défaut (facilement remplaçable),ENTRYPOINTdéfinit l'exécutable principal -
Pour créer des images plus petites en séparant l'environnement de build de l'environnement de production
-
docker logs -f nom_conteneur -
En utilisant des volumes Docker :
-v volume:/chemin/donnees -
Les volumes sont gérés par Docker, les bind mounts lient un chemin spécifique de l'hôte
-
Par résolution DNS automatique en utilisant le nom du conteneur
-
Pour la sécurité : limiter les privilèges en cas de compromission
-
docker run -m 512mou--memory=512m
Résumé
Vous avez maintenant pratiqué :
- La manipulation de conteneurs et d'images
- La création de Dockerfiles optimisés
- La configuration de réseaux et volumes
- Le déploiement d'applications multi-conteneurs
- Les bonnes pratiques de sécurité
- Explorez Docker Swarm pour l'orchestration
- Apprenez Kubernetes pour les déploiements à grande échelle
- Intégrez Docker dans vos pipelines CI/CD