04 - Docker cache, rebuild et zero-downtime
Ce que tu vas apprendre
- Comment le cache Docker fonctionne (et comment il peut te trahir)
- Quand utiliser
--no-cacheet quand s'en passer - Comment ordonner ton Dockerfile pour un cache optimal
- Ce qui se passe pendant un
docker compose up -den production
Prerequisites
- 03 - Écrire le .gitlab-ci.yml : le pipeline est en place
- Comprendre les bases de Docker (images, conteneurs, layers)
Le problème qu'on a eu
Mercredi, 14h. Je pousse un fix CSS sur paltemps.fr. Le pipeline passe, tout est vert. Je rafraichis le site. Le vieux CSS est toujours la. Hard refresh. Toujours l'ancien. Je vide le cache navigateur. Toujours pareil.
J'ai perdu 45 minutes a croire que c'etait un problème de cache navigateur ou de Caddy. En fait, c'etait Docker.
Le Dockerfile copie les fichiers source, lance le build, et produit les assets statiques. Le problème : Docker cache chaque layer. Si le COPY . . détecté que les fichiers n'ont "pas change" (selon son algorithme de checksum), il réutilisé le layer cache. Sauf que parfois le checksum est le meme alors que le contenu a change. Concretement, des modifications dans des fichiers CSS imbriques ne declenchaient pas d'invalidation de cache.
La solution immediate : docker compose build --no-cache.
Comment le cache Docker fonctionne
Docker construit une image layer par layer. Chaque instruction du Dockerfile (FROM, COPY, RUN) créé un layer. Si une instruction et ses inputs n'ont pas change depuis le dernier build, Docker réutilisé le layer existant au lieu de le recalculer.
dockerfileFROM oven/bun:1 AS base # Layer 1 - change rarement
WORKDIR /app # Layer 2 - change jamais
COPY package.json bun.lock ./ # Layer 3 - change quand les deps changent
RUN bun install --frozen-lockfile # Layer 4 - idem
COPY . . # Layer 5 - change a chaque commit
RUN bun run build # Layer 6 - recalcule si layer 5 change
Le cache s'invalide en cascade : si le layer 3 change, les layers 4, 5 et 6 sont recalcules. Mais si seul le layer 5 change, les layers 1 a 4 restent caches. C'est la theorie. En pratique, c'est presque toujours correct. Presque.
Quand le cache ment
Le cache peut servir du contenu obsolète dans quelques situations :
- Les fichiers
.dockerignoreexcluent des fichiers qui influencent le build - Des dépendances système ont ete mises à jour mais le
RUN apt-get installest cache - Des fichiers générés (CSS compile, bundles JS) sont dans le contexte de build
- Le build depend de variables d'environnement non declarees dans le Dockerfile
Pour un projet ou tu deploies 5 fois par jour et ou le build prend 30 secondes, --no-cache est la solution pragmatique. Tu perds 25 secondes de cache, tu gagnes la certitude que ton build est frais.
Les chiffres
J'ai mesure sur notre VPS (2 vCPU, 4 Go RAM) :
| Type de build | Duree | Taille image |
|---|---|---|
| Avec cache (rien change) | ~5 secondes | 180 Mo |
| Avec cache (source change) | ~15 secondes | 180 Mo |
Sans cache (--no-cache) |
~45 secondes | 180 Mo |
45 secondes pour un déploiement complet, build inclus. Sur un projet plus gros avec des dépendances lourdes (node_modules de 500 Mo, build Webpack de 3 minutes), tu voudrais garder le cache pour les dépendances et ne rebuilder que le code source. Mais pour un projet Bun avec un build qui prend 10 secondes, --no-cache n'est pas un problème.
Optimiser l'ordre du Dockerfile
Meme si tu utilises --no-cache en CI, l'ordre de ton Dockerfile compte pour le développement local. La regle : mets les choses qui changent le moins souvent en premier.
dockerfile# 1. Image de base (change jamais)
FROM oven/bun:1 AS base
WORKDIR /app
# 2. Dependances (change quand tu ajoutes un package)
COPY package.json bun.lock ./
RUN bun install --frozen-lockfile
# 3. Code source (change a chaque commit)
COPY . .
RUN bun run build
Si tu inverses et que tu mets COPY . . avant le RUN bun install, chaque modification de code invalide le cache des dépendances. Au lieu de 10 secondes de build local, tu te tapes 60 secondes a reinstaller les node_modules.
Ce qui se passe pendant `docker compose up -d`
Quand tu lances docker compose up -d apres un build, Docker Compose compare les images actuelles avec celles qui tournent. Si l'image d'un service a change, il arrêté l'ancien conteneur et en démarré un nouveau. Les services dont l'image n'a pas change continuent de tourner sans interruption.
Dans notre cas, seul le conteneur app est reconstruit a chaque deploy. Caddy, le serveur mail et Chrome ne bougent pas. Le temps d'arrêt du service app est d'environ 2 a 3 secondes : le temps entre l'arrêt de l'ancien conteneur et le démarrage du nouveau.
C'est du "near-zero-downtime". Pour du vrai zero-downtime, il faudrait un système de blue/green deployment ou de rolling update avec des health checks. Docker Compose seul ne fait pas ca. Mais 2 secondes de downtime a 3h du matin quand personne ne regarde ton site, franchement, c'est acceptable.
Le nettoyage : docker image prune
Chaque docker compose build créé une nouvelle image. L'ancienne n'est plus utilisee mais reste sur le disque. Apres 50 déploiements, tu as 50 images orphelines de 180 Mo chacune. Ca fait 9 Go de dechets.
bashdocker image prune -f
Le -f (force) évité la confirmation interactive. Cette commande supprime toutes les images "dangling" (celles qui n'ont plus de tag et ne sont utilisees par aucun conteneur).
Pour un nettoyage plus agressif (images, volumes, networks inutilises) :
bashdocker system prune -f
Attention avec docker system prune : ca supprime aussi les volumes non utilises. Si tu as des donnees dans des volumes anonymes, tu les perds. Pour notre pipeline, docker image prune -f suffit.
Mon avis pour ton projet
Si ton build total prend moins de 2 minutes : utilise --no-cache et ne te prends pas la tête. La fiabilité vaut plus que 30 secondes de build.
Si ton build prend plus de 5 minutes : optimise ton Dockerfile avec des multi-stage builds, séparé bien les dépendances du code source, et laisse le cache faire son travail. Mais vérifié régulièrement que les assets deploys sont bien à jour.
Navigation : Precedent : 03 - Écrire le .gitlab-ci.yml | Suivant : 05 - Troubleshooting
Sources
- Docker build cache par Docker Docs
- Best practices for Dockerfiles par Docker Docs
- docker compose up référencé par Docker Docs
Retrouve d'autres articles techniques sur paltemps.fr.