Ă la fin de cette page, tu seras capable de :
FROM, WORKDIR, COPY, RUN, EXPOSE, CMD, ENVdocker buildFROM â L'image de base dont on hĂ©rite (point de dĂ©part obligatoire)COPY â Copie des fichiers de ta machine vers l'image en constructionRUN â ExĂ©cute une commande shell pendant la construction de l'imageCMD â DĂ©finit la commande qui s'exĂ©cute au dĂ©marrage du conteneurdocker build â La commande qui lit le Dockerfile et fabrique l'imageJusqu'ici, on a utilisĂ© des images existantes (nginx, mysql, php). Mais que faire quand on veut :
C'est là qu'intervient le Dockerfile : un simple fichier texte qui décrit, étape par étape, comment construire une image sur mesure.
Mon code source + Dockerfile â docker build â Mon Image â docker run â Conteneur
Docker lit le Dockerfile de haut en bas et crée une nouvelle couche à chaque instruction. Ces couches sont mises en cache : si rien n'a changé, Docker ne les reconstruit pas.
FROM â L'image de baseFROM nginx:1.25
FROM php:8.3-apache
FROM node:20-alpine
FROM ubuntu:22.04
alpine dĂ©signe des images trĂšs lĂ©gĂšres basĂ©es sur Alpine Linux (quelques Mo)WORKDIR â Le rĂ©pertoire de travailWORKDIR /var/www/html
Définit le répertoire courant pour les instructions suivantes (COPY, RUN, CMD). Crée le dossier s'il n'existe pas.
FROM php:8.3-apache
WORKDIR /var/www/html
# Maintenant, toutes les instructions suivantes
# s'exécutent dans /var/www/html
COPY â Copier des fichiers# Copier un fichier
COPY index.php .
# Copier un dossier entier
COPY src/ .
# Copier avec un chemin destination explicite
COPY config/php.ini /usr/local/etc/php/php.ini
Syntaxe : COPY source destination
source : chemin relatif sur ta machine (Ă cĂŽtĂ© du Dockerfile)destination : chemin dans l'image en constructionRUN â ExĂ©cuter une commande lors de la construction# Installer des paquets Linux
RUN apt-get update && apt-get install -y curl zip
# Installer une extension PHP
RUN docker-php-ext-install pdo pdo_mysql
# Installer des dépendances npm
RUN npm install
# Créer un dossier
RUN mkdir -p /var/log/monapp
đĄ Bonne pratique : EnchaĂźner les commandes avec
&&dans un seulRUNpour limiter le nombre de couches et réduire la taille de l'image.
# â CrĂ©e 3 couches inutiles
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y zip
# â
Une seule couche
RUN apt-get update && apt-get install -y curl zip
EXPOSE â Documenter le portEXPOSE 80
EXPOSE 3000
EXPOSE 8080
Cette instruction documente le port sur lequel l'application écoute. Elle ne publie pas le port sur la machine hÎte (ça, c'est -p dans docker run). C'est une indication pour les utilisateurs de l'image.
ENV â Variables d'environnementENV APP_ENV=production
ENV PORT=3000
ENV DB_HOST=localhost
Ces variables sont disponibles dans le conteneur pendant son exécution.
FROM node:20-alpine
ENV NODE_ENV=production
ENV PORT=3000
WORKDIR /app
COPY . .
RUN npm install
EXPOSE 3000
CMD ["node", "server.js"]
CMD â La commande de dĂ©marrageCMD ["nginx", "-g", "daemon off;"]
CMD ["php", "-S", "0.0.0.0:80"]
CMD ["node", "server.js"]
CMD par Dockerfiledocker run, elle remplace CMD["commande", "argument"] (forme exec) â plus fiable que la forme texteđĄ Les images officielles dĂ©finissent dĂ©jĂ un
CMD. Par exemple,nginxdémarre nginx,mysqldémarre le serveur MySQL. Tu n'as souvent pas besoin de le redéfinir.
ENTRYPOINT â Point d'entrĂ©e fixe (avancĂ©)ENTRYPOINT ["python", "app.py"]
Similaire Ă CMD mais ne peut pas ĂȘtre remplacĂ© par docker run. On l'utilise quand l'image est dĂ©diĂ©e Ă un seul outil. Moins courant pour les dĂ©butants.
ADD â Copier (avec des super-pouvoirs)ADD archive.tar.gz /var/www/html/
ADD https://exemple.com/fichier.txt /tmp/
Comme COPY, mais peut aussi dĂ©compresser des archives et tĂ©lĂ©charger depuis une URL. Dans la pratique, prĂ©fĂšre COPY pour la lisibilitĂ© â ADD est rĂ©servĂ© aux cas oĂč on a vraiment besoin de ses fonctionnalitĂ©s supplĂ©mentaires.
ARG â Argument de constructionARG VERSION=1.0
ARG ENV=dev
Comme ENV, mais disponible uniquement pendant la construction (pas à l'exécution). Utile pour paramétrer un build.
docker build --build-arg VERSION=2.0 -t mon-app .
| Instruction | Moment d'exécution | RÎle principal |
|---|---|---|
FROM |
Construction | Image de base |
WORKDIR |
Construction | Répertoire de travail |
COPY |
Construction | Copier des fichiers locaux |
ADD |
Construction | Copier + décompresser / URL |
RUN |
Construction | Exécuter une commande |
ENV |
Construction + Exécution | Variable d'environnement |
ARG |
Construction uniquement | ParamĂštre de build |
EXPOSE |
â (documentation) | Documenter le port |
CMD |
Démarrage du conteneur | Commande par défaut |
ENTRYPOINT |
Démarrage du conteneur | Point d'entrée fixe |
docker build# Syntaxe générale
docker build -t nom:tag chemin
# Construire depuis le dossier courant (le plus courant)
docker build -t mon-site:1.0 .
# Construire avec un Dockerfile ailleurs
docker build -t mon-site -f chemin/vers/Dockerfile .
# Forcer la reconstruction sans cache
docker build --no-cache -t mon-site .
| Option | Signification |
|---|---|
-t nom:tag |
Donner un nom et une version Ă l'image |
. |
Le contexte de build : le dossier que Docker envoie au moteur |
-f |
Spécifier un Dockerfile à un autre emplacement |
--no-cache |
Ne pas utiliser le cache des couches précédentes |
Objectif : Créer une image Docker qui sert une page HTML custom via Nginx.
Ce que tu vas apprendre : FROM, COPY, docker build, docker run, le contexte de build
Crée le dossier suivant sur ton Bureau :
docker-html/
âââ Dockerfile
âââ html/
âââ index.html
Crée html/index.html :
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Mon premier conteneur</title>
<style>
body {
font-family: sans-serif;
background: #1a1a2e;
color: #e0e0e0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
margin: 0;
}
h1 { color: #00d4ff; font-size: 3rem; }
p { font-size: 1.2rem; opacity: 0.8; }
</style>
</head>
<body>
<h1>đł Hello Docker !</h1>
<p>Ce site est servi depuis un conteneur Docker.</p>
<p>Image : Nginx 1.25 â construite avec mon Dockerfile</p>
</body>
</html>
Crée le fichier Dockerfile (sans extension) à la racine du projet :
# On part de l'image officielle Nginx
FROM nginx:1.25
# On copie notre dossier html/ dans le répertoire
# par défaut que Nginx utilise pour servir les fichiers
COPY html/ /usr/share/nginx/html/
# Documentation : Nginx écoute sur le port 80
EXPOSE 80
# Nginx est déjà configuré comme CMD dans l'image officielle,
# pas besoin de le redéfinir ici
Ouvre un terminal dans le dossier docker-html/ :
docker build -t mon-site:1.0 .
Analyse de la sortie :
[1/2] FROM docker.io/library/nginx:1.25 â tĂ©lĂ©chargement de l'image de base
[2/2] COPY html/ /usr/share/nginx/html/ â copie de notre fichier
Successfully built abc123def456
Successfully tagged mon-site:1.0 â image créée avec succĂšs â
Vérifie que l'image apparaßt dans la liste :
docker images
Tu dois voir mon-site avec le tag 1.0.
docker run -d --name test-site -p 8080:80 mon-site:1.0
Ouvre http://localhost:8080 â tu vois ta page HTML đ
Modifie le <h1> dans index.html (change le texte).
Reconstruction :
docker build -t mon-site:1.1 .
ArrĂȘte l'ancien conteneur et lance le nouveau :
docker stop test-site
docker rm test-site
docker run -d --name test-site -p 8080:80 mon-site:1.1
Recharge la page : la modification est visible.
đĄ Observation : La reconstruction est quasi-instantanĂ©e. Docker rĂ©utilise le cache de la couche
FROM nginx:1.25(inchangée) et ne recalcule que la coucheCOPY.
docker stop test-site
docker rm test-site
docker rmi mon-site:1.0 mon-site:1.1
Objectif : Créer une image Docker qui contient une vraie mini-application PHP (plusieurs fichiers, connexion à une base de données).
Ce que tu vas apprendre : RUN pour installer des extensions, ENV, image PHP officielle, variables d'environnement au runtime
docker-php-app/
âââ Dockerfile
âââ docker-compose.yml
âââ .env
âââ src/
âââ index.php
âââ config.php
âââ pages/
âââ liste.php
âââ ajouter.php
src/config.php â connexion Ă la base de donnĂ©es :
<?php
// On lit les variables d'environnement injectées par Docker
$host = getenv('DB_HOST') ?: 'db';
$dbname = getenv('DB_NAME') ?: 'apptest';
$user = getenv('DB_USER') ?: 'root';
$password = getenv('DB_PASSWORD') ?: '';
try {
$pdo = new PDO(
"mysql:host=$host;dbname=$dbname;charset=utf8",
$user,
$password,
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
);
} catch (PDOException $e) {
die("Erreur de connexion : " . $e->getMessage());
}
?>
src/index.php â page d'accueil :
<?php
require 'config.php';
// Création de la table si elle n'existe pas
$pdo->exec("CREATE TABLE IF NOT EXISTS messages (
id INT AUTO_INCREMENT PRIMARY KEY,
texte VARCHAR(255) NOT NULL,
cree_le DATETIME DEFAULT CURRENT_TIMESTAMP
)");
?>
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>App Docker PHP</title>
<link rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
</head>
<body class="bg-dark text-light p-4">
<div class="container">
<h1 class="mb-4">đł App PHP dans Docker</h1>
<!-- Formulaire d'ajout -->
<form method="POST" action="pages/ajouter.php" class="mb-4">
<div class="input-group">
<input type="text" name="texte" class="form-control"
placeholder="Ton message..." required>
<button type="submit" class="btn btn-primary">Envoyer</button>
</div>
</form>
<!-- Liste des messages -->
<h2 class="h5 mb-3">Messages enregistrés :</h2>
<?php
$stmt = $pdo->query("SELECT * FROM messages ORDER BY cree_le DESC");
$messages = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (empty($messages)) {
echo '<p class="text-muted">Aucun message pour l\'instant.</p>';
} else {
foreach ($messages as $msg) {
echo '<div class="card bg-secondary mb-2">';
echo ' <div class="card-body py-2">';
echo ' <span>' . htmlspecialchars($msg['texte']) . '</span>';
echo ' <small class="text-muted ms-3">' . $msg['cree_le'] . '</small>';
echo ' </div>';
echo '</div>';
}
}
?>
<p class="mt-4 text-muted small">
Connecté à : <?= $host ?> / <?= $dbname ?>
</p>
</div>
</body>
</html>
src/pages/ajouter.php :
<?php
require '../config.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_POST['texte'])) {
$stmt = $pdo->prepare("INSERT INTO messages (texte) VALUES (?)");
$stmt->execute([htmlspecialchars($_POST['texte'])]);
}
header('Location: ../index.php');
exit;
?>
# Image officielle PHP avec Apache intégré
FROM php:8.3-apache
# Installer les extensions nécessaires
# pdo_mysql : permet Ă PHP de se connecter Ă MySQL via PDO
RUN docker-php-ext-install pdo pdo_mysql
# Définir le répertoire de travail
WORKDIR /var/www/html
# Copier le code source de l'application
COPY src/ .
# Apache écoute sur le port 80 (déjà défini dans l'image de base)
EXPOSE 80
# CMD est déjà défini dans php:8.3-apache
# Il démarre Apache automatiquement
Pourquoi
docker-php-ext-install?
L'imagephp:8.3-apacheest volontairement minimale. Les extensions commepdo_mysqlne sont pas incluses par défaut pour alléger l'image. La commandedocker-php-ext-installest un script fourni par l'image officielle PHP pour les installer proprement.
On ne lance pas le conteneur PHP seul : il a besoin d'une base de données MySQL.
services:
# Le service base de données
db:
image: mysql:8.0
container_name: phpapp_db
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
MYSQL_DATABASE: ${DB_NAME}
volumes:
- db_data:/var/lib/mysql
networks:
- appnet
# Notre application PHP (construite depuis le Dockerfile)
app:
build: . # â Docker va lire le Dockerfile du dossier courant
container_name: phpapp_web
restart: unless-stopped
ports:
- "8080:80"
environment:
DB_HOST: db # â le nom du service MySQL dans le rĂ©seau Docker
DB_NAME: ${DB_NAME}
DB_USER: root
DB_PASSWORD: ${DB_PASSWORD}
networks:
- appnet
depends_on:
- db
volumes:
db_data:
networks:
appnet:
DB_NAME=apptest
DB_PASSWORD=monmotdepasse
# Se placer dans le dossier docker-php-app/
cd docker-php-app
# Construire l'image PHP et démarrer tous les services
docker compose up -d --build
L'option --build force Docker à (re)construire l'image depuis le Dockerfile avant de démarrer.
Surveille le démarrage :
docker compose logs -f
Attends que MySQL soit prĂȘt (tu verras ready for connections dans les logs). Appuie sur Ctrl+C pour quitter les logs sans arrĂȘter les conteneurs.
Ouvre http://localhost:8080
# ArrĂȘter et supprimer les conteneurs
docker compose down
# Relancer (sans --build cette fois, l'image est déjà construite)
docker compose up -d
# Ouvrir http://localhost:8080 â les messages sont toujours lĂ â
Modifie le titre <h1> dans src/index.php.
# Reconstruire uniquement le service "app" et redémarrer
docker compose up -d --build app
Recharge la page â la modification est visible.
# ArrĂȘter les services, supprimer les conteneurs, le rĂ©seau ET les volumes
docker compose down -v
# Supprimer les images construites
docker rmi docker-php-app-app
.dockerignoreComme .gitignore, le fichier .dockerignore dit Ă Docker quels fichiers ne pas inclure dans le contexte de build. Ăa accĂ©lĂšre la construction et Ă©vite d'envoyer des fichiers inutiles (node_modules, .env, .git...).
Crée .dockerignore à cÎté du Dockerfile :
.env
.git
*.log
node_modules