Objectif: partir d'un Dockerfile simple puis orchestrer plusieurs services avec Docker Compose.
Fonctionnement: lis la section, applique sur un projet test, puis vérifie les conteneurs et logs.
Un Dockerfile est un fichier texte contenant une série d'instructions pour construire une image Docker de manière automatisée et reproductible.
# Commentaire FROM image-de-base LABEL maintainer="vous@example.com" RUN commande-shell COPY source destination WORKDIR /chemin/travail EXPOSE 80 CMD ["executable", "param1", "param2"]
| Instruction | Description | Exemple |
|---|---|---|
FROM |
Image de base (obligatoire) | FROM ubuntu:22.04 |
RUN |
Exécute une commande lors du build | RUN apt-get update |
COPY |
Copie fichiers hôte → image | COPY app.py /app/ |
ADD |
Comme COPY + décompression archives | ADD archive.tar.gz /app/ |
WORKDIR |
Définit le répertoire de travail | WORKDIR /app |
ENV |
Définit une variable d'environnement | ENV APP_ENV=production |
EXPOSE |
Documente les ports utilisés | EXPOSE 80 443 |
VOLUME |
Crée un point de montage | VOLUME /data |
USER |
Utilisateur pour RUN/CMD/ENTRYPOINT | USER www-data |
CMD |
Commande par défaut (écrasable) | CMD ["nginx", "-g", "daemon off;"] |
ENTRYPOINT |
Point d'entrée (non écrasable) | ENTRYPOINT ["python", "app.py"] |
ARG |
Variable de build | ARG VERSION=1.0 |
mon-apache/
├── Dockerfile
├── html/
│ ├── index.html
│ └── style.css
└── conf/
└── custom.conf
# Utiliser l'image officielle Apache FROM httpd:2.4 # Métadonnées LABEL maintainer="vous@example.com" LABEL version="1.0" LABEL description="Serveur Apache personnalisé" # Copier la configuration personnalisée COPY conf/custom.conf /usr/local/apache2/conf/extra/httpd-custom.conf # Ajouter l'inclusion dans la config principale RUN echo "Include conf/extra/httpd-custom.conf" >> /usr/local/apache2/conf/httpd.conf # Copier le site web COPY html/ /usr/local/apache2/htdocs/ # Définir les permissions RUN chown -R www-data:www-data /usr/local/apache2/htdocs/ # Exposer le port HTTP EXPOSE 80 # La commande par défaut est déjà définie dans l'image de base # CMD ["httpd-foreground"]
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Mon Apache Docker</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>Serveur Apache dans Docker</h1>
<p>Cette page est servie par Apache depuis un conteneur Docker personnalisé.</p>
<ul>
<li>Image de base : httpd:2.4</li>
<li>Configuration personnalisée</li>
<li>Site web statique</li>
</ul>
</body>
</html>
# Configuration personnalisée Apache
ServerTokens Prod
ServerSignature Off
TraceEnable Off
<Directory "/usr/local/apache2/htdocs">
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>
# Construire l'image cd mon-apache docker build -t mon-apache:1.0 . # Vérifier l'image créée docker images mon-apache # Exécuter le conteneur docker run -d -p 8080:80 --name apache-custom mon-apache:1.0 # Tester curl http://localhost:8080 # Voir les logs docker logs apache-custom # Arrêter et supprimer docker stop apache-custom docker rm apache-custom
Chaque instruction crée un layer (couche). Docker met en cache ces layers pour accélérer les builds.
FROM ubuntu:22.04 # Chaque RUN crée un layer RUN apt-get update RUN apt-get install -y nginx RUN apt-get install -y curl RUN apt-get install -y vim RUN apt-get clean COPY index.html /var/www/html/ CMD ["nginx", "-g", "daemon off;"]
FROM ubuntu:22.04
# Regrouper les commandes RUN
RUN apt-get update && \
apt-get install -y \
nginx \
curl \
vim && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
# COPY en dernier (change souvent)
COPY index.html /var/www/html/
CMD ["nginx", "-g", "daemon off;"]
# Ne pas copier dans l'image .git .gitignore README.md docker-compose.yml Dockerfile node_modules *.log .env .vscode
Utiliser plusieurs FROM dans un seul Dockerfile pour séparer la compilation de l'exécution.
# Stage 1 : Build FROM golang:1.21 AS builder WORKDIR /app COPY go.mod go.sum ./ RUN go mod download COPY . . RUN CGO_ENABLED=0 GOOS=linux go build -o myapp # Stage 2 : Runtime (image finale légère) FROM alpine:latest RUN apk --no-cache add ca-certificates WORKDIR /root/ COPY --from=builder /app/myapp . EXPOSE 8080 CMD ["./myapp"] # Image builder : ~1 Go # Image finale : ~10 Mo !
# Stage 1 : Dependencies FROM node:18 AS deps WORKDIR /app COPY package*.json ./ RUN npm ci --only=production # Stage 2 : Build FROM node:18 AS builder WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build # Stage 3 : Runtime FROM node:18-alpine WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY --from=builder /app/dist ./dist EXPOSE 3000 CMD ["node", "dist/server.js"]
Docker Compose permet de définir et gérer des applications multi-conteneurs via un fichier YAML. Au lieu de lancer plusieurs docker run, on définit tout dans docker-compose.yml.
version: '3.8' # Version du format (optionnel depuis 2020)
services: # Définition des conteneurs
service1:
image: nginx
ports:
- "8080:80"
service2:
build: ./app
volumes:
- ./data:/data
volumes: # Volumes nommés
mon-volume:
networks: # Réseaux personnalisés
mon-reseau:
# Démarrer tous les services (mode détaché) docker compose up -d # Démarrer avec rebuild des images docker compose up -d --build # Voir les services actifs docker compose ps # Voir les logs docker compose logs docker compose logs -f service-name # Arrêter tous les services docker compose stop # Arrêter et supprimer les conteneurs docker compose down # Arrêter et supprimer TOUT (conteneurs, réseaux, volumes) docker compose down -v # Exécuter une commande dans un service docker compose exec service-name bash # Voir la configuration finale docker compose config
# Partie 1 : Méthode manuelle (complexe) docker network create lamp-network docker volume create mysql-data docker run -d \ --name lamp-mysql \ --network lamp-network \ -v mysql-data:/var/lib/mysql \ -e MYSQL_ROOT_PASSWORD=rootpass \ -e MYSQL_DATABASE=myapp \ -e MYSQL_USER=appuser \ -e MYSQL_PASSWORD=apppass \ mysql:8.0 docker run -d \ --name lamp-apache \ --network lamp-network \ -p 8080:80 \ -v $(pwd)/html:/var/www/html \ php:8.2-apache docker exec lamp-apache docker-php-ext-install mysqli docker restart lamp-apache
lamp-stack/
├── docker-compose.yml
├── Dockerfile
├── html/
│ └── index.php
└── mysql/
└── init.sql
services:
# Service MySQL
db:
image: mysql:8.0
container_name: lamp-mysql
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: rootpass
MYSQL_DATABASE: myapp
MYSQL_USER: appuser
MYSQL_PASSWORD: apppass
volumes:
- mysql-data:/var/lib/mysql
- ./mysql/init.sql:/docker-entrypoint-initdb.d/init.sql
networks:
- lamp-network
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
# Service Apache + PHP
web:
build: .
container_name: lamp-apache
restart: unless-stopped
ports:
- "8080:80"
volumes:
- ./html:/var/www/html
networks:
- lamp-network
depends_on:
db:
condition: service_healthy
environment:
DB_HOST: db
DB_NAME: myapp
DB_USER: appuser
DB_PASS: apppass
volumes:
mysql-data:
name: lamp-mysql-data
networks:
lamp-network:
name: lamp-network
driver: bridge
FROM php:8.2-apache # Installer les extensions PHP nécessaires RUN docker-php-ext-install mysqli pdo pdo_mysql # Activer mod_rewrite Apache RUN a2enmod rewrite # Créer le répertoire de travail WORKDIR /var/www/html # Exposer le port EXPOSE 80
<?php
// Les variables viennent de docker-compose.yml
$host = getenv('DB_HOST');
$db = getenv('DB_NAME');
$user = getenv('DB_USER');
$pass = getenv('DB_PASS');
try {
$pdo = new PDO("mysql:host=$host;dbname=$db", $user, $pass);
echo "<h1>✅ LAMP Stack avec Docker Compose</h1>";
echo "<p>Version MySQL : " . $pdo->query('SELECT VERSION()')->fetchColumn() . "</p>";
// Créer une table
$pdo->exec("CREATE TABLE IF NOT EXISTS visitors (
id INT AUTO_INCREMENT PRIMARY KEY,
visit_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)");
$pdo->exec("INSERT INTO visitors () VALUES ()");
$count = $pdo->query("SELECT COUNT(*) FROM visitors")->fetchColumn();
echo "<p>Nombre de visites : $count</p>";
} catch (PDOException $e) {
echo "<h1>❌ Erreur de connexion</h1>";
echo "<p>" . htmlspecialchars($e->getMessage()) . "</p>";
}
?>
-- Script d'initialisation MySQL
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL,
email VARCHAR(100) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO users (username, email) VALUES
('admin', 'admin@example.com'),
('user1', 'user1@example.com');
# Démarrer la stack docker compose up -d # Vérifier que tout fonctionne docker compose ps # Accéder à l'application http://localhost:8080 # Voir les logs docker compose logs -f # Accéder à MySQL docker compose exec db mysql -u appuser -papppass myapp # Arrêter proprement docker compose down # Tout supprimer (y compris les données) docker compose down -v
| Aspect | Partie 1 (docker run) | Partie 2 (docker compose) |
|---|---|---|
| Commandes | ~15 lignes | 1 seule : docker compose up -d |
| Configuration | Lignes de commande | Fichier YAML lisible |
| Dépendances | Manuelles (ordre important) | Automatiques (depends_on) |
| Réseau | Création manuelle | Créé automatiquement |
| Volumes | Création manuelle | Créé automatiquement |
| Reproductibilité | Documenter les commandes | Fichier versionné (Git) |
| Partage | Difficile | Simple (1 fichier) |
┌────────────────┐ ┌────────────────┐
│ Frontend │─────▶│ Backend │
│ (React) │ │ (Node.js) │
│ Port 3000 │ │ Port 5000 │
└────────────────┘ └────────┬───────┘
│
┌────────▼───────┐ ┌────────────────┐
│ PostgreSQL │ │ Redis │
│ Port 5432 │ │ Port 6379 │
└────────────────┘ └────────────────┘
services:
# Frontend React
frontend:
build: ./frontend
ports:
- "3000:3000"
volumes:
- ./frontend/src:/app/src
environment:
- REACT_APP_API_URL=http://localhost:5000
depends_on:
- backend
# Backend Node.js
backend:
build: ./backend
ports:
- "5000:5000"
volumes:
- ./backend/src:/app/src
environment:
- NODE_ENV=development
- DB_HOST=postgres
- DB_PORT=5432
- DB_NAME=myapp
- DB_USER=postgres
- DB_PASS=secret
- REDIS_HOST=redis
- REDIS_PORT=6379
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_started
# Base de données PostgreSQL
postgres:
image: postgres:15-alpine
environment:
POSTGRES_DB: myapp
POSTGRES_USER: postgres
POSTGRES_PASSWORD: secret
volumes:
- postgres-data:/var/lib/postgresql/data
- ./postgres/init.sql:/docker-entrypoint-initdb.d/init.sql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
# Cache Redis
redis:
image: redis:7-alpine
command: redis-server --appendonly yes
volumes:
- redis-data:/data
# Interface d'administration PostgreSQL
adminer:
image: adminer
ports:
- "8080:8080"
depends_on:
- postgres
volumes:
postgres-data:
redis-data:
FROM node:18-alpine WORKDIR /app # Copier les fichiers de dépendances COPY package*.json ./ RUN npm ci # Copier le code source COPY . . EXPOSE 5000 CMD ["npm", "run", "dev"]
FROM node:18-alpine WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . EXPOSE 3000 CMD ["npm", "start"]
# Démarrer tous les services docker compose up -d # Services disponibles : # - Frontend : http://localhost:3000 # - Backend : http://localhost:5000 # - Adminer (DB) : http://localhost:8080 # Rebuilder un service spécifique docker compose up -d --build backend # Voir les logs d'un service docker compose logs -f backend # Exécuter une commande dans un service docker compose exec backend npm install express docker compose exec postgres psql -U postgres myapp # Redémarrer un service docker compose restart backend # Arrêter tout docker compose down
# Configuration de l'application APP_NAME=MonApp APP_ENV=development APP_PORT=5000 # Base de données DB_HOST=postgres DB_PORT=5432 DB_NAME=myapp DB_USER=postgres DB_PASS=secret123 # Redis REDIS_HOST=redis REDIS_PORT=6379 # Secrets (NE PAS COMMITER !) JWT_SECRET=super-secret-key API_KEY=abc123def456
services:
backend:
build: ./backend
ports:
- "${APP_PORT}:5000"
environment:
- NODE_ENV=${APP_ENV}
- DB_HOST=${DB_HOST}
- DB_PORT=${DB_PORT}
- DB_NAME=${DB_NAME}
- DB_USER=${DB_USER}
- DB_PASS=${DB_PASS}
- JWT_SECRET=${JWT_SECRET}
env_file:
- .env # Ou directement comme ça
postgres:
image: postgres:15-alpine
environment:
POSTGRES_DB: ${DB_NAME}
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASS}
# Configuration de l'application APP_NAME=MonApp APP_ENV=development APP_PORT=5000 # Base de données DB_HOST=postgres DB_PORT=5432 DB_NAME=myapp DB_USER=postgres DB_PASS=CHANGEZ_MOI # Secrets JWT_SECRET=CHANGEZ_MOI API_KEY=CHANGEZ_MOI
services:
backend:
build: ./backend
postgres:
image: postgres:15-alpine
# Service de développement uniquement
adminer:
image: adminer
ports:
- "8080:8080"
profiles:
- dev
# Service de test uniquement
test-runner:
build: ./backend
command: npm test
profiles:
- test
# Développement (avec adminer) docker compose --profile dev up -d # Test docker compose --profile test up # Production (services de base uniquement) docker compose up -d
# docker-compose.yml (base)
services:
backend:
build: ./backend
# docker-compose.dev.yml (développement)
services:
backend:
volumes:
- ./backend/src:/app/src
environment:
- NODE_ENV=development
# docker-compose.prod.yml (production)
services:
backend:
restart: always
environment:
- NODE_ENV=production
# Développement docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d # Production docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d # Ou avec variable d'environnement export COMPOSE_FILE=docker-compose.yml:docker-compose.dev.yml docker compose up -d
container_name pour faciliter le débogageunless-stopped ou always en productionservices:
backend:
build: ./backend
container_name: myapp-backend
restart: unless-stopped
# Limites de ressources
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
reservations:
cpus: '0.25'
memory: 256M
# Health check
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:5000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
# Logging
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
# Variables d'environnement
env_file:
- .env
# Volumes
volumes:
- ./backend/src:/app/src:ro # Read-only
# Réseau
networks:
- backend-network
# Dépendances
depends_on:
postgres:
condition: service_healthy
postgres:
image: postgres:15-alpine
container_name: myapp-postgres
restart: unless-stopped
# Variables d'environnement
environment:
POSTGRES_DB: ${DB_NAME}
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
# Secrets (Docker Swarm)
secrets:
- db_password
# Volumes
volumes:
- postgres-data:/var/lib/postgresql/data
# Health check
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USER}"]
interval: 10s
timeout: 5s
retries: 5
# Réseau isolé
networks:
- backend-network
volumes:
postgres-data:
driver: local
networks:
backend-network:
driver: bridge
secrets:
db_password:
file: ./secrets/db_password.txt
# Voir la configuration finale (avec variables résolues) docker compose config # Valider le fichier sans démarrer docker compose config --quiet # Lister les services docker compose ps # Voir les services avec les ports docker compose ps -a # Logs de tous les services docker compose logs -f # Logs d'un service spécifique docker compose logs -f backend # Suivre les logs avec timestamps docker compose logs -f -t # Exécuter une commande docker compose exec backend bash docker compose exec postgres psql -U postgres # Lancer un one-off container docker compose run backend npm install docker compose run --rm backend npm test # Rebuilder sans cache docker compose build --no-cache # Pull des nouvelles versions d'images docker compose pull # Redémarrer un service docker compose restart backend # Pause/Unpause docker compose pause docker compose unpause # Voir les processus docker compose top # Voir les événements en temps réel docker compose events # Supprimer les volumes orphelins docker compose down --volumes --remove-orphans
Mission: décrire et lancer une application multi-services complète avec Compose, profils et variables d'environnement.