Docker pas à pas en treize techniques
Hello World / Docker Web / Docker AppUne slide concept, une slide exemple. Quelques solutions.FROM, COPY de base, RUN, cycle docker build / run / stop, notions image, conteneur, port
COPY vers un chemin absolu dans l'image (là où le serveur web cherche le contenu) ; déclaration du port applicatif côté image avec EXPOSE et publication vers l'hôte au run avec -p hote:conteneur ; scripts du devoir (construire.sh, demarrer.sh, arreter.sh)
FROM php:apache (image applicative), plusieurs COPY vers destinations différentes
Image avec 1 service déjà installé + RUN apt-get install du 2e service ; copier un script, le rendre exécutable, le lancer au build (RUN) ou au lancement (ENTRYPOINT) ; orchestrer plusieurs processus ; premier démarrage idempotent avec un fichier témoin (initialise.flag) pour ne pas rejouer l'init à chaque démarrage
Build d'app JS dans le conteneur (npm install + npm run build), ARG au build, chaîne ARG -> ENV -> NEXT_PUBLIC_* ; .dockerignore et .gitignore (absolument nécessaires dès qu'on touche à Node, node_modules/ hors contexte de build et hors dépôt Git)
Socket X11 de l'hôte monté dans le conteneur (/tmp/.X11-unix, DISPLAY, xhost +local:docker)
Ressource externe lourde : téléchargée au build avec RUN wget (modèle léger figé dans l'image) ou au premier run dans un volume monté (modèle lourd) ; choix selon poids, fréquence et autonomie
Hello World - les bases du Dockerfile
Quatre paires pour comprendre la recette Docker : FROM, CMD, RUN, WORKDIR, COPY et ENV. Les briques que tout le reste reutilise.
FROM ... + CMD [...]
Les deux lignes minimales d'un Dockerfile.
FROM choisit l'image de base. Toujours la PREMIERE instruction. CMD declare la commande lancee au DEMARRAGE du conteneur. Forme JSON conseillee : CMD ["echo", "texte"] -> pas de shell intermediaire, signaux propres a l'arret. Sans FROM et CMD, le Dockerfile n'est pas executable.
FROM alpine CMD ["echo", "Bonjour"]
$ docker build -t bonjour . -> Successfully tagged bonjour:latest $ docker run --rm bonjour Bonjour # Le --rm efface le conteneur # des qu'il a fini de tourner.
RUN <commande>
Executer une commande PENDANT la construction de l'image.
Chaque ligne RUN cree une nouvelle COUCHE dans l'image. Le resultat (fichier ecrit, paquet installe, ...) est fige. Difference cruciale : RUN -> au BUILD, modifie l'IMAGE. CMD -> au DEMARRAGE, lance le processus du conteneur. On RUN apt-get install une fois ; on RUN curl le binaire au build ; on RUN echo > /info pour deposer un fichier dans l'image.
FROM alpine RUN echo "bonjour depuis l'image" > /message.txt CMD ["cat", "/message.txt"]
$ docker build -t message . => RUN echo "bonjour ..." > /message.txt => CACHED si pas de changement $ docker run --rm message bonjour depuis l'image
WORKDIR ... + COPY ... ...
Apporter un fichier du poste hote dans l'image.
WORKDIR cree (si besoin) et entre dans un dossier de l'image. Toutes les instructions suivantes (RUN, COPY, CMD) partent de la. COPY source destination apporte un fichier de l'hote vers l'image. Source : relative au CONTEXTE de build (le . dans docker build). Sans COPY, l'image ne contient que ce que l'image de base a livre.
FROM alpine WORKDIR /app COPY message.txt . CMD cat message.txt
$ echo 'recette du jour' > message.txt $ docker build -t fichier . $ docker run --rm fichier recette du jour # Modifier message.txt, rebuild, # le contenu suit.
FROM alpine WORKDIR /app COPY message.txt . # CMD cat message.txt CMD ls -la /app
$ docker build -t fichier . $ docker run --rm fichier drwxr-xr-x 1 root root . drwxr-xr-x 1 root root .. -rw-r--r-- 1 root root message.txt # Le fichier est bien la : # on remet CMD cat message.txt
ENV NOM=valeur
Variable d'environnement persistante, surchargeable au demarrage.
ENV declare une variable disponible au build ET au runtime. Lisible dans un RUN, dans CMD, dans le code de l'application. Au demarrage, --env NOM=autreValeur (ou -e NOM=...) ecrase la valeur, sans rebuild. Usage typique : configurer un comportement (verbosite, locale, ou parametres d'un script) sans toucher au code.
FROM alpine ENV MESSAGE=monde CMD echo "Bonjour $MESSAGE !"
$ docker build -t enviro .
$ docker run --rm enviro
Bonjour monde !
$ docker run --rm \
-e MESSAGE=tortue enviro
Bonjour tortue !ARG NOM + ENV NEXT_PUBLIC_NOM=${NOM}Pour qu'une variable du build arrive jusqu'au code Javascript qui tourne dans le navigateur, il faut la passer par trois etapes.
ARG : visible au build, disparait apres. ENV : survit dans l'image, lisible au runtime. Pour Next.js : les variables prefixees NEXT_PUBLIC_ sont injectees dans le bundle JS au moment de npm run build, donc visibles cote navigateur. Chaine complete : ARG (docker build) -> ENV NEXT_PUBLIC_NOM (runtime Node) -> string en dur dans le bundle JS apres npm run build -> lisible dans React via process.env.NEXT_PUBLIC_NOM Equivalent Vite : prefixe VITE_ ou config define.
FROM node:20-slim
WORKDIR /app
ARG NOM_ETUDIANT=anonyme
ARG MATRICULE=000000
ENV NEXT_PUBLIC_NOM=${NOM_ETUDIANT}
ENV NEXT_PUBLIC_MATRICULE=${MATRICULE}
COPY . .
RUN npm install && npm run build
CMD ["npm", "start"]docker build \ --build-arg NOM_ETUDIANT="Camille" \ --build-arg MATRICULE="AB12345" \ -t borne-accueil:1.0 . # Dans le code React : # const nom = process.env.NEXT_PUBLIC_NOM; # // -> "Camille" (en dur dans le bundle)
FROM ... + CMD [...]
FROM php:apache, FROM mariadb, FROM node:20 (vs FROM alpine)
Une image de Docker Hub qui contient deja l'outil principal (PHP+Apache, MariaDB, Node, Python...) au lieu de partir d'une distribution vide. On gagne du temps, on evite les erreurs d'installation. Deux familles d'images de base : 1) Images de DISTRIBUTION (alpine, debian, ubuntu) : vide, on installe tout. Utile pour des cas particuliers ou des combinaisons exotiques. 2) Images APPLICATIVES (php:apache, node:20, mariadb, python:3.12) : l'outil principal est deja installe et configure pour Docker. La doc officielle explique ce que l'image fournit deja. Regle generale pour ce TP : si une image officielle existe pour votre stack, PARTEZ DE LA. On n'installe pas Apache + PHP sur Ubuntu pour les amener au meme etat que php:apache - c'est du travail deja fait.
# A partir d'Ubuntu vide (long, fragile)
FROM ubuntu:24.04
RUN apt-get update && apt-get install -y \
apache2 php libapache2-mod-php
RUN a2enmod php8.1
COPY index.php /var/www/html/
EXPOSE 80
CMD ["apache2-foreground"]
# A partir de php:apache (court, robuste)
FROM php:apache
COPY index.php /var/www/html/Site PHP -> FROM php:apache
Site statique -> FROM nginx:alpine
Site Node -> FROM node:20
App Python -> FROM python:3.12-slim
Base de donnees -> FROM mariadb:11 ou postgres:16
App leger custom-> FROM alpine + RUN apt-get
Regle : voir hub.docker.com pour les images
officielles de votre stack avant
d'installer manuellement.Docker Web - servir un service HTTP
Six paires pour empaqueter un site, ajouter -p et --restart, monter du contenu evolutif, orchestrer plusieurs services et alleger l'image avec un build multi-stage.
EXPOSE <port>
Documenter le port que le service ecoute dans le conteneur.
EXPOSE est INFORMATIF. Il NE PUBLIE PAS le port vers l'hote. La publication vers l'hote, c'est -p hote:conteneur dans docker run. Pourquoi EXPOSE alors : - lisible par docker inspect (auto-documentation), - exploite par des outils comme docker-compose ou un proxy, - rappelle a l'usager quel port mapper au run. Sans EXPOSE, on peut quand meme mapper avec -p, mais on devine.
FROM httpd RUN echo "allo" > /usr/local/apache2/htdocs/index.html EXPOSE 80
$ docker build -t toile .
$ docker run -d \
-p 8086:80 \
--name toile-actif toile
$ curl http://localhost:8086
allo
$ docker stop toile-actif
$ docker rm toile-actifconstruire.sh + demarrer.sh + arreter.sh
Trois scripts bash qui remplacent les longues commandes Docker.
Chaque image livree a son triplet de scripts dans le repertoire : construire.sh -> docker build -t <nom>:<version> . demarrer.sh -> docker run -d -p <hote>:<conteneur> --name ... arreter.sh -> docker stop ... && docker rm ... Avantages : - une seule source de verite pour les ports et les noms, - documentation executable du projet, - exigence du devoir pour chaque demande.
FROM httpd COPY src/ /usr/local/apache2/htdocs/ EXPOSE 80
#!/bin/bash set -e nomImage="toile-libre" versionImage="1.0" docker build \ -t "$nomImage:$versionImage" \ -t "$nomImage:latest" .
#!/bin/bash set -e nomImage="toile-libre" nomConteneur="toile-libre-actif" portHote="8087" docker run \ --detach \ --name "$nomConteneur" \ --publish "$portHote:80" \ "$nomImage:latest"
COPY moteur/ /var/www/html/ + COPY contenu/ /var/www/html/contenu/
Quand l'application livree separe moteur et contenu, on les COPY a des destinations differentes dans l'image pour reconstituer la structure qu'attend le serveur.
Les fournisseurs livrent souvent un projet avec une separation claire : moteur/ - le code PHP (fige cote technique), fp-content/ - les fichiers que les editeurs peuvent deposer. Dans l'image, le serveur web cherche tout au meme endroit (par exemple /var/www/html/ pour php:apache). On fait donc DEUX COPY : - le moteur dans /var/www/html/ - le contenu en sous-dossier /var/www/html/contenu/ Premiere fois qu'on ecrit plusieurs COPY vers des cibles differentes.
FROM php:8.2-apache # Le moteur PHP a la racine servie COPY moteur/ /var/www/html/ # Le contenu en sous-dossier visible COPY fp-content/ /var/www/html/fp-content/ EXPOSE 80
/var/www/html/
index.php # depuis moteur/
article.php # depuis moteur/
decoration/ # depuis moteur/
fp-content/ # depuis fp-content/
articles/
festival.html
pollinisateurs.html
L'image refait la structure qu'attend le moteur.--restart unless-stopped
Le conteneur revient automatiquement, sauf si on l'a arrete a la main.
Une borne en libre service doit survivre aux coupures de courant et aux redemarrages de Docker. C'est le role de --restart. Strategies disponibles : no ne pas redemarrer (defaut). on-failure seulement si le conteneur a sorti != 0. always toujours, meme apres un docker stop. unless-stopped toujours, SAUF apres un docker stop. Pour le devoir : unless-stopped sur toutes les bornes.
FROM nginx:alpine COPY contenu/ /usr/share/nginx/html/ EXPOSE 80
$ docker run \
--detach \
--name borne-cantons-actif \
--publish 8080:80 \
--restart unless-stopped \
borne-cantons:1.0
# Apres un reboot du poste
$ docker ps
STATUS
Up 12 seconds borne-cantons-actifif [ ! -f ".../initialise.flag" ]
Un fichier temoin qui marque que l'initialisation a deja ete faite. La prochaine fois, on detecte sa presence et on saute le bloc init.
Probleme : l'ENTRYPOINT s'execute a CHAQUE docker run / docker start. Si l'init (creer la base, jouer le schema) y est, elle se rejoue chaque fois. C'est inutile au mieux, destructeur au pire. Solution : apres la premiere init, on cree un fichier temoin. Aux demarrages suivants, le if detecte sa presence et saute le bloc init. Pattern reutilisable pour TOUTE initialisation a faire une seule fois.
#!/bin/bash set -e service mariadb start until mysqladmin ping --silent; do sleep 1; done if [ ! -f "/var/lib/mysql/initialise.flag" ]; then mysql < /schema.sql touch /var/lib/mysql/initialise.flag fi exec apache2-foreground
$ docker run -d --name catalogue ... service mariadb start Initialisation du schema... Schema initialise. Lancement d'Apache. $ docker stop catalogue && docker start catalogue service mariadb start Lancement d'Apache. # init sautee
touch "$cheminTemoin"
Le fichier flag, c'est un fichier qu'on a mis exprès :
if [ ! -f "$cheminTemoin" ]; then
mysql < schema.sql
touch "$cheminTemoin" # <-- crée le flag
else
# on saute l'init
fi
À quoi ça sert : éviter de réimporter le SQL (et écraser) à chaque docker run
Le fichier flag = c'est la présence du modèle dans le cache :
if ollama list | grep -q "qwen2.5"; then
# modèle déjà là, on saute
else
ollama pull qwen2.5:0.5b # téléchargement de 400 Mo
fi
À quoi ça sert : éviter de re-télécharger 400 Mo de LLM à chaque redémarrage.
ENV DEBIAN_FRONTEND=noninteractive RUN apt-get install -y --no-install-recommends
Un script qui enleve l'interactivite
FROM debian:12 RUN apt-get update RUN apt-get install -y --no-install-recommends mariadb-server apache2
ENTRYPOINT ["/usr/local/bin/..."]
Un script qui orchestre plusieurs processus puis bascule sur le principal.
ENTRYPOINT fixe la commande qui sera TOUJOURS lancee au demarrage. Difference avec CMD : ENTRYPOINT ne se laisse pas remplacer par les arguments du docker run (sauf --entrypoint explicite). Usage typique : un script bash qui prepare l'environnement (lancement d'un service en arriere plan, attente, initialisation de la base), puis finit par exec <processus principal>. Le exec remplace le shell par le processus -> le conteneur reste rattache au bon PID 1 et reagit proprement aux signaux.
FROM debian:12 RUN apt-get update && apt-get install -y mariadb-server apache2 COPY demarrer-services.sh /usr/local/bin/ RUN chmod +x /usr/local/bin/demarrer-services.sh EXPOSE 80 ENTRYPOINT ["/usr/local/bin/demarrer-services.sh"]
#!/bin/bash set -e service mariadb start until mysqladmin ping --silent; do sleep 1 done if [ ! -f /var/lib/mysql/init.flag ]; then mysql < /entree/schema.sql touch /var/lib/mysql/init.flag fi # Bascule sur le principal exec apache2-foreground
Docker App - app avec deps et donnees
Des paires pour passer du hello world a une vraie application : dockerignore obligatoire, ordre des couches pour le cache, parametres injectes au build, et donnees lourdes hors de l'image, dans un volume.
fichier .dockerignore
Un fichier voisin du Dockerfile, cousin du .gitignore, qui dit a Docker ce qu'il ne faut PAS envoyer au build
Au docker build, Docker envoie tout le dossier courant au demon qui build le conteneur. Sans .dockerignore : .git/, node_modules/, donnees-bd/ et cie partent aussi. => Resultat : build lent, image potentiellement polluee, secrets exposes. Critique des qu'on a un projet Node (node_modules pese des centaines de Mo). Ce dossier est complètement regénéré par le npm build, alors il doit être listé dans le .dockerignore et aussi dans le .gitignore. Un projet avec donnees locales (donnees-bd/, sons/, caches) servies sur un volume ne doit pas les copier aussi dans l'image. Symetrique au .gitignore : meme philosophie, autre destinataire.
.git *.log node_modules donnees-bd construire.sh demarrer.sh arreter.sh README.md
$ docker build -t toile-libre . Sending build context to Docker daemon -> 12 kB # au lieu de 350 Mo sans .dockerignore # Sans le fichier, la meme commande envoie # node_modules/, .git/, donnees-bd/ : enorme, # inutile, et a chaque rebuild.
Mettre les couches STABLES en haut, les VOLATILES en bas.
Mettre les couches STABLES en haut, les VOLATILES en bas.
Docker met chaque couche en cache. Une couche est invalidee si le fichier qu'elle copie change, OU si la ligne change. Une fois invalidee, toutes les couches suivantes le sont aussi. Strategie : COPY requirements.txt puis RUN pip install AVANT le COPY . du code. Resultat : - modifier app.py -> rebuild instantane. - ajouter une dependance -> rebuild seulement de pip + suite.
FROM node:20 + npm install + npm run build + npm start
L'approche d'initiation pour la borne d'accueil Next.js : un Dockerfile classique en une etape qui contient TOUT (Node, sources, node_modules, build). Lisible, simple, chaque ligne se comprend. Pour cette étape : un seul FROM, un seul flux de commandes.
C'est ACCEPTABLE pour ce labo : la priorite est la comprehension. Ordre des couches pour profiter du cache de build : 1) COPY package*.json (fichiers stables) 2) RUN npm install (lent mais reste en cache si package*.json ne change pas) 3) COPY . . (le code, qui change souvent) 4) RUN npm run build Une modification du code -> rebuild rapide (npm install reste en cache). L'image finale est plus grosse (~300 a 500 Mo) parce qu'elle embarque Node, npm, les sources et tout le contenu de node_modules.
FROM node:20
WORKDIR /app
ARG NOM_ETUDIANT=anonyme
ARG MATRICULE=000000
ENV NEXT_PUBLIC_NOM=${NOM_ETUDIANT}
ENV NEXT_PUBLIC_MATRICULE=${MATRICULE}
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
EXPOSE 3000
CMD ["npm", "start"]#!/bin/bash set -e docker run -d \ --name borne-accueil \ -p 8090:3000 \ --restart unless-stopped \ borne-accueil:1.0 # Le visiteur ouvre http://localhost:8090
# Pygame
FROM python:3.12-slim
RUN apt-get update && \
apt-get install -y libsdl2-2.0-0
RUN pip install pygame
COPY quiz.py .
CMD ["python", "quiz.py"]
# three.js
FROM node:20
WORKDIR /app
COPY . .
RUN npm install && npm run build
CMD ["npx", "serve", "dist"]# Pygame (X11, pas de port) xhost +local:docker docker run --rm --name quiz \ -e DISPLAY=$DISPLAY \ -v /tmp/.X11-unix:/tmp/.X11-unix \ borne-quiz:1.0 # Qt = idem + audio Pulse -v /run/user/$(id -u)/pulse/native:/run/... # three.js (port HTTP, pas de X11) docker run -d --name jeu3d -p 8091:3000 \ borne-jeu3d:1.0
-v /tmp/.X11-unix:/tmp/.X11-unix + -e DISPLAY
Pour les apps qui ne sont pas des serveurs web (Pygame, Qt) mais doivent dessiner une fenetre sur l'ecran de l'hote. On monte le socket X11.
Pas de port reseau a publier : c'est une app desktop, pas un serveur HTTP. Le serveur d'affichage X11 de l'hote ecoute sur un socket Unix. On monte ce socket dans le conteneur (volume bind tres specifique). Le programme dans le conteneur lit DISPLAY et parle au socket monte. xhost +local:docker autorise les connexions locales avant le run. xhost -local:docker apres pour refermer la securite. Meme principe pour le son : monter le socket PulseAudio.
FROM python:3.12-slim
WORKDIR /app
RUN apt-get update && \
apt-get install -y --no-install-recommends \
libsdl2-2.0-0 libsdl2-image-2.0-0
RUN pip install pygame
COPY quiz.py .
CMD ["python", "quiz.py"]#!/bin/bash set -e xhost +local:docker docker run --rm \ --name borne-quiz \ -e DISPLAY=$DISPLAY \ -v /tmp/.X11-unix:/tmp/.X11-unix \ borne-quiz:1.0 xhost -local:docker
Differences : port pour three.js, X11 pour Pygame/Qt, audio Pulse en plus pour Qt.
3 stacks, meme travail Docker
| Pygame | app Python + SDL2. PAS de port reseau, X11 oui, audio non | Quiz a choix multiples sur les especes. Stack la plus simple : pip install pygame + CMD python quiz.py |
|---|---|---|
| Qt | app Python + Qt6 (PySide6). PAS de port, X11 oui, audio Pulse | Ajoute le montage du socket Pulse pour la lecture des sons. Generer-sons.py s'execute pendant le build (assets synthetises). |
| three.js | app web Vite/JS. PORT HTTP oui, X11 non, pas d'audio specifique. | Build dans l'image : npm install + npm run build |
- Ce qui change : ports, X11, audio.
- Trois experiences tres differentes, mais l'image Docker se construit de la meme facon : choisir une base, installer les deps, copier le code, definir CMD.
# Pygame
FROM python:3.12-slim
RUN apt-get update && \
apt-get install -y libsdl2-2.0-0
RUN pip install pygame
COPY quiz.py .
CMD ["python", "quiz.py"]
# three.js
FROM node:20
WORKDIR /app
COPY . .
RUN npm install && npm run build
CMD ["npx", "serve", "dist"]# Pygame (X11, pas de port) xhost +local:docker docker run --rm --name quiz \ -e DISPLAY=$DISPLAY \ -v /tmp/.X11-unix:/tmp/.X11-unix \ borne-quiz:1.0 # Qt = idem + audio Pulse -v /run/user/$(id -u)/pulse/native:/run/... # three.js + Electron : meme schema que Pygame/Qt # (apps desktop kiosque, pas de port reseau) xhost +local:docker docker run --rm --name jeu3d \ -e DISPLAY=$DISPLAY \ -v /tmp/.X11-unix:/tmp/.X11-unix \ borne-jeu3d:1.0
RUN wget ... (au build) vs -v cache:/chemin
Pour un modele IA, un binaire, des donnees lourdes : on choisit consciemment si elles vivent DANS l'image (au build) ou DANS un volume (au runtime).
Pattern A - au build avec RUN wget : + image autonome, fonctionne offline immediatement, - image plus grosse, rebuild = re-telechargement. Bon pour : modeles legers (~10 a 50 Mo), donnees stables. Pattern B - au premier run dans un volume monte : + image legere, telechargement une seule fois entre conteneurs, - depend du reseau au premier demarrage. Bon pour : modeles lourds (~ centaines de Mo), donnees evolutives. Criteres : poids, frequence de mise a jour, autonomie hors-ligne.
FROM python:3.12-slim
RUN apt-get update && \
apt-get install -y wget
RUN wget -O /modele.onnx \
https://...mobilenet.onnx
RUN wget -O /labels.txt \
https://...labels.txt
RUN pip install onnxruntime
COPY vision.py /
CMD ["python", "/vision.py"]FROM python:3.12-slim
RUN curl -fsSL https://ollama.com/install.sh | sh
COPY entrypoint.sh /
ENTRYPOINT ["/entrypoint.sh"]
# au run :
$ docker run -d \
-v zoo-ollama-cache:/root/.ollama \
borne-llm:1.0Et maintenant ?
- Reproduisez chaque paire chez vous, dans l'ordre des trois categories.
- Pour chaque image a construire, demandez : c'est du web ou de l'app ?
- Ecrivez le Dockerfile dans VOTRE contexte, pas a partir d'un modele.
- Si vous bloquez, revenez a la slide concept correspondante.
- Reservez Internet pour les details, pas pour les solutions toutes faites.
FROM python:3.12-slim WORKDIR /app COPY requis.txt . RUN pip install -r requis.txt COPY . . CMD ["python", "app.py"]
$ docker build -t cite-python . # 1er build : 20 s (pip install) # Modifier app.py, rebuild : $ docker build -t cite-python . # 2e build : 1 s (cache pip) $ docker run --rm cite-python Bonjour monde ! ...
BONUS VOLUME
Trois paires pour passer du hello world a une vraie application : ordre des couches pour le cache, parametres injectes au build, et donnees lourdes hors de l'image, dans un volume.
| volume (nomme ou anonyme) | donnees dans /var/lib/docker/volumes/ | gere par Docker |
|---|---|---|
| bind mount | un repertoire de l'hote prete au conteneur | gere par vous |
| tmpfs | de la RAM, disparait a l'arret du conteneur | gere par le noyau |
- Donc techniquement, un volume EST un type de mount.
- Le drapeau moderne --mount type=volume|bind|tmpfs force a nommer le type explicitement.
docker volume create + --volume <nom>:<cible>
Un cache pilote par Docker, partage entre runs et conteneurs.
Different d'un bind mount : ici Docker gere le dossier, pas l'hote. Avantages : - portable (pas de chemin hote a hardcoder), - persiste apres un docker rm du conteneur, - partageable entre plusieurs conteneurs. Usage typique : un modele IA, des donnees de base, un cache npm. On peut creer le volume a l'avance (docker volume create) ou laisser Docker le creer a la volee a la premiere utilisation.
FROM ollama/ollama VOLUME /root/.ollama EXPOSE 11434
# Creer le volume une fois
$ docker volume create cache-modeles
# Premier run : telechargement (~3 min)
$ docker run --detach \
--name borne-narrative-actif \
--volume cache-modeles:/root/.ollama \
borne-narrative:1.0
# Run suivant : instantane
$ docker stop borne-narrative-actif
$ docker rm borne-narrative-actif
$ docker run --detach \
--name borne-narrative-actif \
--volume cache-modeles:/root/.ollama \
borne-narrative:1.0OPTIMISATION
Trois paires pour passer du hello world a une vraie application : ordre des couches pour le cache, parametres injectes au build, et donnees lourdes hors de l'image, dans un volume.
FROM ... AS <nom> + COPY --from=<nom>
Deux etapes dans un meme Dockerfile : on construit avec l'une, on sert avec l'autre.
FROM ... AS <nom> declare une etape de build nommee. Cette etape installe les outils lourds (Node, compilateurs, ...) et produit des fichiers (un dossier dist/, un binaire compile). Ensuite : un second FROM (sans AS) demarre une image FINALE legere. COPY --from=<nom> pioche les fichiers utiles dans l'etape precedente. Resultat : l'image finale ne contient PAS les outils de build. Image Next.js : ~150 Mo au lieu de 1.2 Go.
FROM node:20-slim AS atelier WORKDIR /generation COPY package*.json ./ RUN npm install COPY . . RUN npm run build FROM node:20-slim WORKDIR /borne COPY --from=atelier /generation ./ EXPOSE 3000 CMD ["npm", "start"]
docker stop + docker start (sans rm)
Tant qu'on ne fait pas docker rm, les donnees ecrites dans le conteneur survivent aux arrets et redemarrages. Pas besoin de volume pour ce cas.
Idee fausse repandue : il faudrait un volume pour la persistance. En realite, les donnees ecrites dans le systeme de fichiers du conteneur vivent dans sa derniere couche et survivent a stop/start du meme conteneur. On perd les donnees seulement si on fait docker rm (destruction du conteneur). Demarche pedagogique : 1) Version simple : sans volume, persistance via stop/start. 2) Version avancee : avec volume, ca survit aussi a rm/run.
$ docker run -d --name catalogue -p 8080:80 catalogue:1.0 $ docker exec catalogue mysql -e "INSERT INTO espece ..." # Page web : 13 especes $ docker stop catalogue $ docker start catalogue # Page web : 13 especes (toujours la) $ docker stop catalogue && docker rm catalogue $ docker run -d --name catalogue ... # Page web : 12 especes (perdue)
$ docker rm catalogue
catalogue
-> les donnees ecrites dans la couche du conteneur
viennent d'etre detruites avec lui.
Pour qu'elles survivent au rm aussi : monter un volume
(slide suivante)--volume <hote>:<cible>:ro
Monter un dossier de l'hote dans le conteneur en lecture seule.
Le bind mount expose un dossier de l'hote a un endroit du conteneur. Different d'un volume nomme : ici c'est l'HOTE qui pilote le contenu. Le suffixe :ro (read-only) interdit au conteneur d'ecrire dedans. Utile pour : - du contenu evolutif qu'on met a jour SANS rebuild, - de la securite (le conteneur ne peut pas corrompre le dossier), - du partage entre plusieurs conteneurs lecteurs. BONUS pour le docker web no 2
FROM php:8.2-apache COPY index.php /var/www/html/ EXPOSE 80
# Premier demarrage
$ docker run \
--detach \
--name moteur-festival-actif \
--publish 8081:80 \
--volume "$(pwd)/articles:/var/www/html/articles:ro" \
moteur-festival:1.0
# Ajouter un article cote hote
$ cp nouveau-fest.html articles/
$ curl http://localhost:8081
-> le nouveau article apparait
SANS rebuild de l'image.COPY install-database.sh .
Un script qui initialise
apt-get update echo "ca marche" apt-get install -y --no-install-recommends mariadb-server mariadb-client
SIGNATURE
Trois paires pour passer du hello world a une vraie application : ordre des couches pour le cache, parametres injectes au build, et donnees lourdes hors de l'image, dans un volume.
ARG <nom>=<defaut> + --build-arg <nom>=<valeur>
Une variable qui n'existe QUE pendant le build.
ARG est lisible pendant docker build, puis disparait.
Au build, --build-arg NOM=valeur ecrase la valeur par defaut.
Pour qu'une valeur ARG survive dans l'image au runtime, on la
RECOPIE dans une ENV :
ARG NOM_ETUDIANT=anonyme
ENV NOM_ETUDIANT=${NOM_ETUDIANT}
Cas d'usage du devoir :
- banniere d'identification (nom, matricule, date de build),
- choix d'une variante au build sans toucher au Dockerfile.FROM httpd
ARG NOM_ETUDIANT=anonyme
ARG MATRICULE=000000
ARG BUILD_DATE=inconnu
ENV NOM_ETUDIANT=${NOM_ETUDIANT}
ENV MATRICULE=${MATRICULE}
ENV BUILD_DATE=${BUILD_DATE}
COPY signature.html /usr/local/apache2/htdocs/
EXPOSE 80docker build \
--build-arg NOM_ETUDIANT="Camille" \
--build-arg MATRICULE="AB12345" \
--build-arg BUILD_DATE="$(date -I)" \
-t borne-cetaces:1.0 .
# Verifier
$ docker inspect borne-cetaces:1.0 \
--format '{{ .Config.Env }}'