
Ce guide vous apprend à construire une image reproductible, petite, rapide à builder, et à la publier proprement. Vous comprendrez le raisonnement derrière chaque décision de build, pas juste les options.
À la fin, vous saurez :
- Optimiser le cache pour des builds en quelques secondes
- Utiliser multi-stage pour diviser la taille par 10
- Passer des secrets sans les exposer dans l'image
- Publier vers un registry avec un digest vérifiable
Droit au but
Section intitulée « Droit au but »Les quatre raccourcis ci-dessous mènent directement aux sections qui changent le plus vos builds. Si vous cherchez un gain immédiat, commencez par le cache : c'est l'optimisation qui demande le moins de travail pour le plus d'effet.
Modèle mental : comment podman build fonctionne
Section intitulée « Modèle mental : comment podman build fonctionne »Avant d'optimiser quoi que ce soit, il faut savoir qui fait le travail et sur quoi il travaille. Deux notions suffisent : le moteur de build réellement invoqué par podman build, et le contexte de build, c'est-à-dire l'ensemble des fichiers qui lui sont transmis. La quasi-totalité des builds lents ou cassés s'explique par l'une de ces deux notions mal comprise.
Podman utilise Buildah en interne
Section intitulée « Podman utilise Buildah en interne »Quand vous exécutez podman build, Podman appelle Buildah pour construire l'image. Cela a des implications importantes :
| Aspect | Ce que ça signifie |
|---|---|
| Stockage partagé | podman et buildah voient les mêmes images |
| Compatibilité | Dockerfile et Containerfile sont équivalents |
| Options avancées | Certaines options viennent de Buildah (--secret, --ssh) |
| Sans démon | Pas de daemon à maintenir, builds rootless possibles |
Le contexte de build
Section intitulée « Le contexte de build »Le contexte est le dossier envoyé au builder. Tout ce qui est dedans peut être copié dans l'image avec COPY.
# Le "." à la fin = contexte = dossier courantpodman build -t mon-image:1.0 .Problème fréquent : un dossier node_modules/ de 500 MB dans le contexte ralentit tous les builds.
Solution : le fichier .containerignore (voir section cache).
Premier build reproductible
Section intitulée « Premier build reproductible »Un build reproductible signifie : même Containerfile, même image, demain comme dans 6 mois. La principale source de dérive n'est pas votre code mais l'image de base : un tag comme python:3.12 pointe vers un contenu différent à chaque correctif publié en amont. Le premier réflexe consiste donc à figer cette référence, puis à écrire un Containerfile minimal que l'on mesurera avant de l'optimiser.
Règle 1 : pinner la version de base
Section intitulée « Règle 1 : pinner la version de base »Un tag est une étiquette mobile : le mainteneur peut la faire pointer ailleurs à tout moment. Plus le tag est précis, moins il bouge, et seul le digest est totalement immuable.
# ❌ Mauvais : "latest" peut changer demainFROM python:3.14.6
# ❌ Mauvais encore : "3.12" peut passer de 3.12.1 à 3.12.2FROM python:3.12-alpine
# ✅ Bon : version exacte + digest pour la reproductibilitéFROM python:3.12.8-alpine3.21Notre premier Containerfile
Section intitulée « Notre premier Containerfile »Cette première version est volontairement naïve : elle marche, mais elle ignore le cache et embarque tout le contexte. Elle sert de point de comparaison chiffré pour les optimisations qui suivent. L'arborescence du projet tient en quatre fichiers.
Répertoiremon-api/
- Containerfile
- requirements.txt
Répertoireapp/
- init .py
- main.py
FROM python:3.12.8-alpine3.21
LABEL org.opencontainers.image.title="Mon API"LABEL org.opencontainers.image.version="1.0.0"
WORKDIR /app
# Copier tout et installerCOPY . .RUN pip install --no-cache-dir -r requirements.txt
# Utilisateur non-rootRUN adduser -D appuserUSER appuser
EXPOSE 8000CMD ["python", "-m", "uvicorn", "app.main:app", "--host", "0.0.0.0"]Construire et mesurer
Section intitulée « Construire et mesurer »Sans mesure de départ, impossible de démontrer un gain. Relevez systématiquement deux chiffres, la taille finale et le temps de build, avant toute modification du Containerfile.
-
Construire l'image
Fenêtre de terminal time podman build -t mon-api:v1-naive .Notez le temps de build et la taille.
-
Mesurer la taille
Fenêtre de terminal podman images | grep mon-apiRésultat localhost/mon-api v1-naive abc123 30 seconds ago 287 MB -
Voir les layers
Fenêtre de terminal podman image history mon-api:v1-naive -
Tester
Fenêtre de terminal podman run --rm -p 8000:8000 mon-api:v1-naive# Dans un autre terminal: curl http://localhost:8000
Constat : 287 MB et 45 secondes de build. On peut faire beaucoup mieux.
Le vrai levier : le cache
Section intitulée « Le vrai levier : le cache »Le cache est l'optimisation la plus importante et elle ne coûte qu'un réordonnancement d'instructions. Un build bien caché passe de 2 min à 10 sec, sans rien changer au contenu de l'image produite. Tout repose sur un principe unique : Podman réutilise le résultat d'une instruction tant que ni cette instruction, ni les fichiers qu'elle copie, ni aucune instruction précédente n'ont changé.
Comment le cache fonctionne
Section intitulée « Comment le cache fonctionne »Podman met en cache chaque instruction (chaque RUN, COPY, etc. crée un "layer"). Si une instruction n'a pas changé, le layer en cache est réutilisé.
Mais : dès qu'un layer change, tous les layers suivants sont invalidés.
L'erreur classique : tout copier trop tôt
Section intitulée « L'erreur classique : tout copier trop tôt »Placer COPY . . avant l'installation des dépendances suffit à annuler tout le bénéfice du cache : la moindre virgule modifiée dans le code change le layer copié, donc invalide l'installation qui suit.
# ❌ Cache invalidé à chaque modification de codeFROM python:3.12.8-alpine3.21WORKDIR /appCOPY . . # ← Change à chaque commitRUN pip install -r requirements.txt # ← Rebuild 100% du temps !La solution : séparer dépendances et code
Section intitulée « La solution : séparer dépendances et code »Le remède consiste à copier d'abord le seul fichier dont dépend l'installation, ici requirements.txt, puis le code applicatif. L'installation reste alors en cache tant que la liste des dépendances ne bouge pas.
# ✅ Dépendances installées une seule foisFROM python:3.12.8-alpine3.21@sha256:ba13ef990f6e5d13014e9e8d04c02a8fdb0fe53d6dccf6e19147f316e6cc3a84WORKDIR /app
# 1. Copier UNIQUEMENT les fichiers de dépendancesCOPY requirements.txt .
# 2. Installer (en cache tant que requirements.txt ne change pas)RUN pip install --no-cache-dir -r requirements.txt
# 3. Copier le code (change souvent, mais pip est déjà fait)COPY . .
CMD ["python", "-m", "uvicorn", "app.main:app", "--host", "0.0.0.0"]Mesurer l'impact
Section intitulée « Mesurer l'impact »La démonstration tient en trois commandes : un build sans cache, une modification de code, puis un rebuild. L'option --no-cache sert ici à obtenir une mesure de référence honnête, sans réutilisation de layers antérieurs.
# Premier build (cold cache)time podman build --no-cache -t mon-api:v2-cache .# → 45 secondes
# Modifier un fichier Pythonecho "# comment" >> app/main.py
# Deuxième build (warm cache)time podman build -t mon-api:v2-cache .# → 3 secondes ! (pip install est en cache)Le fichier .containerignore
Section intitulée « Le fichier .containerignore »Excluez du contexte tout ce qui n'est pas nécessaire au build :
# Contrôle de version.git/.gitignore
# Fichiers de développement*.mdREADME*docs/tests/
# Environnements virtuels (installés dans l'image).venv/venv/__pycache__/*.pyc
# Secrets et config locale.env.env.*secrets/*.key*.pem
# Fichiers de buildContainerfile*.containerignore.dockerignoreMesurer l'impact :
# Avant .containerignoredu -sh mon-api/# 500 MB (avec .venv et .git)
# Après .containerignore (effectif dans le build)tar -cvf - --exclude-from=.containerignore . | wc -c# 50 KBQuand utiliser --no-cache
Section intitulée « Quand utiliser --no-cache »--no-cache est un outil de diagnostic, pas un mode normal.
| Situation | Utilisez |
|---|---|
| Build de développement | Cache normal |
| CI/CD répétitif | Cache normal |
| "Mon build semble buggé" | --no-cache pour debug |
| Image finale avant release | --no-cache (optionnel) |
Multi-stage : le standard
Section intitulée « Multi-stage : le standard »Le multi-stage build sépare l'environnement de compilation de l'environnement d'exécution. C'est le pattern standard pour les applications compilées.
Le problème : les outils de compilation restent dans l'image
Section intitulée « Le problème : les outils de compilation restent dans l'image »Installer gcc ou les en-têtes de développement d'une base de données est souvent indispensable pour compiler une dépendance native. Le souci, c'est que ces paquets restent dans l'image finale alors qu'aucun processus ne s'en sert au démarrage.
# Image de développement = tout installéFROM python:3.12-slim@sha256:57cd7c3a7a273101a6485ba99423ee568157882804b1124b4dd04266317710de
RUN apt-get update && apt-get install -y \ gcc libpq-dev # Nécessaires au build seulement
COPY requirements.txt .RUN pip install -r requirements.txt
COPY . .CMD ["python", "app.py"]Taille : 400 MB (gcc et libpq-dev inclus, inutiles au runtime).
La solution : multi-stage
Section intitulée « La solution : multi-stage »Un Containerfile peut enchaîner plusieurs FROM. Chaque FROM ouvre un stage, et seul le dernier produit l'image publiée. L'instruction COPY --from=builder récupère uniquement les artefacts utiles du stage précédent, ce qui laisse les compilateurs derrière.
# Stage 1: Builder (avec outils de compilation)FROM python:3.12.8-alpine3.21 AS builder
RUN apk add --no-cache gcc musl-dev libffi-dev
WORKDIR /installCOPY requirements.txt .
# Installer dans un prefix isoléRUN pip install --no-cache-dir --prefix=/install -r requirements.txt
# Stage 2: Runtime (minimal)FROM python:3.12.8-alpine3.21 AS runtime
# Copier UNIQUEMENT les packages installésCOPY --from=builder /install /usr/local
WORKDIR /appCOPY app/ ./app/
# Utilisateur non-rootRUN adduser -D appuserUSER appuser
EXPOSE 8000CMD ["python", "-m", "uvicorn", "app.main:app", "--host", "0.0.0.0"]Construire et comparer
Section intitulée « Construire et comparer »Les deux images cohabitent dans le stockage local, ce qui permet de lire l'écart directement dans podman images. La colonne de droite donne la taille décompressée sur disque, pas la taille transférée au registry, qui est plus faible car compressée.
# Build multi-stagepodman build -t mon-api:v3-multistage .
# Comparer les taillespodman images | grep mon-apilocalhost/mon-api v3-multistage def456 10 seconds ago 52 MBlocalhost/mon-api v1-naive abc123 30 seconds ago 287 MBGain : 82% de réduction (287 MB → 52 MB).
Cibler un stage spécifique
Section intitulée « Cibler un stage spécifique »Parfois vous voulez construire uniquement le stage builder (pour debug ou tests) :
# Construire uniquement le builderpodman build --target builder -t mon-api:builder .
# Le stage builder contient gcc, parfait pour les testspodman run --rm mon-api:builder python -m pytestExemple multi-stage Go (binaire statique)
Section intitulée « Exemple multi-stage Go (binaire statique) »Go pousse le principe à son extrême : avec CGO_ENABLED=0, le compilateur produit un binaire statique qui n'a besoin d'aucune bibliothèque système. Le stage d'exécution peut alors partir de scratch, une base vide qui ne contient strictement aucun fichier.
# BuilderFROM golang:1.24-alpine AS builder
WORKDIR /buildCOPY go.mod go.sum ./RUN go mod download
COPY . .RUN CGO_ENABLED=0 go build -o /app
# Runtime (FROM scratch = 0 MB)FROM scratch
COPY --from=builder /app /appENTRYPOINT ["/app"]Taille finale : 8 MB (juste le binaire statique).
Sécurité : secrets au build
Section intitulée « Sécurité : secrets au build »Certains builds nécessitent des secrets : token npm, clé SSH pour un repo privé, credentials pip. Ne les mettez jamais en ARG ou ENV, ils seraient visibles dans l'historique de l'image.
Le problème : un secret passé en ARG reste consultable
Section intitulée « Le problème : un secret passé en ARG reste consultable »Un ARG est enregistré dans les métadonnées de l'image, et le fichier écrit par le RUN persiste dans le layer correspondant. Supprimer le fichier plus loin ne change rien : le layer précédent reste dans l'image et n'importe qui capable de la télécharger peut le lire.
# ❌ DANGEREUX : le token est dans l'image finaleARG NPM_TOKENRUN echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" >> ~/.npmrcRUN npm install# N'importe qui peut extraire le tokenpodman history mon-image --no-trunc | grep NPM_TOKENLa solution : --secret
Section intitulée « La solution : --secret »L'option --secret monte un fichier secret temporairement pendant le build, sans le persister dans l'image.
FROM node:20-alpine
WORKDIR /appCOPY package*.json ./
# Le secret est disponible UNIQUEMENT pendant ce RUNRUN --mount=type=secret,id=npmrc,target=/root/.npmrc \ npm install
COPY . .CMD ["node", "index.js"]# Créer le fichier secretecho "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > /tmp/npmrc
# Build avec le secretpodman build --secret id=npmrc,src=/tmp/npmrc -t mon-app:secure .Vérification : le secret n'est pas dans l'image.
podman history mon-app:secure --no-trunc | grep -i token# (aucun résultat)Exemples de secrets au build
Section intitulée « Exemples de secrets au build »Le mécanisme est le même dans les trois cas : le fichier est monté à l'emplacement attendu par l'outil, le temps d'un seul RUN. Notez la différence du dernier onglet : type=ssh ne monte pas un fichier mais expose la socket de l'agent SSH, ce qui évite de faire entrer la clé privée dans le build.
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc \ npm installpodman build --secret id=npmrc,src=$HOME/.npmrc .RUN --mount=type=secret,id=pip,target=/root/.pip/pip.conf \ pip install -r requirements.txtpodman build --secret id=pip,src=$HOME/.pip/pip.conf .RUN --mount=type=ssh \ git clone git@github.com:private/repo.giteval $(ssh-agent)ssh-add ~/.ssh/id_ed25519podman build --ssh default .Formats d'image : OCI vs Docker
Section intitulée « Formats d'image : OCI vs Docker »Podman produit des images au format OCI (Open Container Initiative) par défaut. C'est le format standard, supporté par tous les registries modernes. Le format historique de Docker reste accepté par Podman pour les cas de compatibilité, mais il n'apporte aucun avantage fonctionnel.
| Format | Default Podman | Compatible |
|---|---|---|
| OCI | ✅ | Tous registries modernes |
| Docker | Non | Legacy, Docker Hub |
Quand forcer un format
Section intitulée « Quand forcer un format »Le format se choisit au moment du build avec --format. Le cas qui justifie de repasser en format Docker est un registry ancien ou un outil interne qui refuse le manifeste OCI.
# Format OCI (défaut)podman build --format oci -t mon-image:oci .
# Format Docker (compatibilité legacy)podman build --format docker -t mon-image:docker .Règle : gardez OCI sauf si vous avez un problème de compatibilité spécifique.
Publier proprement
Section intitulée « Publier proprement »L'objectif : l'image est poussée vers un registry et vous avez conservé son digest, l'empreinte cryptographique qui identifie ce contenu exact et rien d'autre. Un tag se réécrit, un digest non : c'est lui qui permettra plus tard de prouver quelle image tournait en production, de la signer ou de lui rattacher un SBOM (Software Bill of Materials, l'inventaire des composants d'un logiciel).
Workflow complet
Section intitulée « Workflow complet »Les six étapes suivantes forment le trajet minimal entre une image locale et une release traçable. Les trois premières sont locales et réversibles ; le push ne l'est pas toujours, car beaucoup de registries interdisent d'écraser un tag déjà publié.
-
Tag sémantique
Fenêtre de terminal # Tag de développementpodman tag mon-api:v3-multistage mon-api:1.0.0podman tag mon-api:v3-multistage mon-api:1.0podman tag mon-api:v3-multistage mon-api:1podman tag mon-api:v3-multistage mon-api:latest -
Tag pour le registry
Fenêtre de terminal podman tag mon-api:1.0.0 registry.example.com/team/mon-api:1.0.0 -
Authentification
Fenêtre de terminal podman login registry.example.com -
Push
Fenêtre de terminal podman push registry.example.com/team/mon-api:1.0.0 -
Récupérer le digest
Fenêtre de terminal podman inspect registry.example.com/team/mon-api:1.0.0 \--format '{{.Digest}}'Résultat sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -
Documenter
Conservez le digest dans votre documentation de release :
Release 1.0.0Image: registry.example.com/team/mon-api:1.0.0Digest: sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
Tags vs Digests
Section intitulée « Tags vs Digests »Les deux identifiants ne servent pas au même usage et ne se remplacent pas. Le tableau ci-dessous résume la seule propriété qui compte vraiment en production : la mutabilité.
| Tags | Digests | |
|---|---|---|
| Mutable | Oui (:latest peut changer) | Non (identifiant crypto) |
| Lisible | Oui (:1.0.0) | Non (sha256:abc...) |
| Traçable | Non | Oui (SBOM, signatures) |
Règle : utilisez les tags pour la lisibilité, conservez les digests pour la traçabilité.
Push vers différents registries
Section intitulée « Push vers différents registries »La séquence ne change pas d'un registry à l'autre : authentification, tag complet incluant le nom d'hôte, puis push. Seule l'authentification varie, et l'onglet GitHub illustre la bonne pratique : --password-stdin évite que le jeton apparaisse dans l'historique du shell ou dans la liste des processus.
podman login docker.iopodman tag mon-api:1.0.0 docker.io/monuser/mon-api:1.0.0podman push docker.io/monuser/mon-api:1.0.0echo $GITHUB_TOKEN | podman login ghcr.io -u USERNAME --password-stdinpodman tag mon-api:1.0.0 ghcr.io/monuser/mon-api:1.0.0podman push ghcr.io/monuser/mon-api:1.0.0podman login registry.example.compodman tag mon-api:1.0.0 registry.example.com/team/mon-api:1.0.0podman push registry.example.com/team/mon-api:1.0.0Options avancées (cas particuliers)
Section intitulée « Options avancées (cas particuliers) »Ces options sont utiles dans des cas spécifiques. Ne les utilisez pas par défaut.
--squash : fusionner les layers
Section intitulée « --squash : fusionner les layers »--squash fusionne tous les layers en un seul.
| Avantage | Inconvénient |
|---|---|
| Image plus petite (un seul layer) | Perd l'historique des layers |
| Pas d'artefacts de build visibles | Cache inutilisable pour builds incrémentaux |
| Incompatible avec SBOM par layer |
# Squash toutpodman build --squash -t mon-image:squashed .
# Squash uniquement les layers ajoutés (garde le base layer)podman build --squash-all -t mon-image:all-squashed .Quand utiliser : si la traçabilité des layers n'est pas importante et que vous voulez une image minimale.
--layers : contrôler la création de layers
Section intitulée « --layers : contrôler la création de layers »Cette option décide si chaque instruction produit son propre layer intermédiaire. Passer à --layers=false réduit le nombre de couches mais prive les builds suivants de tout point de reprise, donc de cache.
# Créer un layer par instruction (défaut)podman build --layers -t mon-image:layered .
# Tout dans un seul layer (comme --squash mais différent internement)podman build --layers=false -t mon-image:single .--pull : contrôler le téléchargement de l'image de base
Section intitulée « --pull : contrôler le téléchargement de l'image de base »Cette option décide si Podman va rechercher une version plus récente de l'image de base avant de construire. Elle n'a plus d'effet pratique si vous référencez la base par digest, puisque le digest désigne déjà un contenu unique.
# Pull uniquement si l'image n'existe pas localement (défaut)podman build --pull=missing -t mon-image .
# Toujours pull (pour avoir la dernière version)podman build --pull=always -t mon-image .
# Jamais pull (utiliser le cache local)podman build --pull=never -t mon-image .Dépannage
Section intitulée « Dépannage »La grande majorité des échecs de build se ramène à une poignée de causes, presque toujours liées au contexte ou à l'ordre des instructions. Le tableau ci-dessous sert de premier tri ; les commandes qui suivent permettent d'aller chercher la cause exacte quand le symptôme ne suffit pas.
Problèmes courants
Section intitulée « Problèmes courants »Lisez ce tableau du symptôme vers la solution : la colonne du milieu explique pourquoi le symptôme apparaît, ce qui évite d'appliquer un correctif au hasard.
| Symptôme | Cause | Solution |
|---|---|---|
COPY failed: file not found | Fichier hors contexte ou dans .containerignore | Vérifier le chemin et .containerignore |
| Build très lent | Contexte trop gros | Ajouter .containerignore |
| Cache jamais utilisé | Ordre des instructions | Mettre les deps avant le code |
| Image trop grosse | Pas de multi-stage | Utiliser multi-stage |
| Secret visible dans history | ARG/ENV pour secrets | Utiliser --secret |
| Push échoue | Pas authentifié | podman login |
Commandes de diagnostic
Section intitulée « Commandes de diagnostic »Ces commandes répondent chacune à une question précise : ce que fait le build, ce que pèse chaque layer, ce qui part réellement dans le contexte, et ce que contient l'image produite. La dernière est la vérification à faire avant toute publication d'une image construite avec un secret.
# Voir les logs détailléspodman build --log-level debug -t mon-image .
# Voir la taille de chaque layerpodman image history mon-image:1.0 --no-trunc
# Vérifier le contexte envoyétar -cvf - . 2>&1 | head -20
# Analyser le contenu de l'imagepodman run --rm -it mon-image:1.0 sh
# Vérifier qu'un secret n'est pas dans l'imagepodman history mon-image:1.0 --no-trunc | grep -i secretLe cache ne fonctionne pas
Section intitulée « Le cache ne fonctionne pas »Symptôme : chaque build refait tout.
Diagnostic :
# Voir quel layer invalide le cachepodman build -t test . 2>&1 | grep -E "(STEP|Using cache|COMMIT)"Causes fréquentes :
COPY . .trop tôt- Fichiers qui changent toujours dans le contexte (
.git/, logs) --no-cacheutilisé par erreur
Lab : construire une image de A à Z
Section intitulée « Lab : construire une image de A à Z »Objectif : construire une API FastAPI optimisée et la publier. L'exercice reprend les trois leviers du guide dans l'ordre où ils se mesurent : une version naïve pour établir la référence, une version multi-stage avec le cache correctement ordonné, puis un rebuild pour constater le gain. Comptez une quinzaine de minutes, connexion au registry comprise.
-
Créer le projet
Fenêtre de terminal mkdir -p ~/lab-build/app && cd ~/lab-buildapp/main.py from fastapi import FastAPIapp = FastAPI()@app.get("/")def read_root():return {"message": "Hello from optimized image!"}requirements.txt fastapi==0.115.0uvicorn[standard]==0.32.0 -
Build naïf (baseline)
Containerfile.naive FROM python:3.12-slimWORKDIR /appCOPY . .RUN pip install -r requirements.txtCMD ["uvicorn", "app.main:app", "--host", "0.0.0.0"]Fenêtre de terminal time podman build -f Containerfile.naive -t api:naive .podman images | grep apiNotez : taille et temps de build.
-
Build optimisé (multi-stage + cache)
Containerfile # BuilderFROM python:3.12.8-alpine3.21 AS builderWORKDIR /installCOPY requirements.txt .RUN pip install --no-cache-dir --prefix=/install -r requirements.txt# RuntimeFROM python:3.12.8-alpine3.21COPY --from=builder /install /usr/localWORKDIR /appCOPY app/ ./app/RUN adduser -D appuser && chown -R appuser /appUSER appuserEXPOSE 8000CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0"]Fenêtre de terminal time podman build -t api:optimized .podman images | grep api -
Mesurer le gain cache
Fenêtre de terminal # Modifier le codeecho "# comment" >> app/main.py# Rebuild (devrait être rapide)time podman build -t api:optimized .Le rebuild devrait prendre < 5 secondes (pip en cache).
-
Publier (optionnel)
Fenêtre de terminal # Tagpodman tag api:optimized registry.example.com/demo/api:1.0.0# Pushpodman login registry.example.compodman push registry.example.com/demo/api:1.0.0# Digestpodman inspect registry.example.com/demo/api:1.0.0 --format '{{.Digest}}'
Résultats attendus :
| Métrique | Naïf | Optimisé | Gain |
|---|---|---|---|
| Taille | ~300 MB | ~50 MB | -83% |
| Build initial | 45 sec | 30 sec | -33% |
| Rebuild (code change) | 45 sec | 3 sec | -93% |
À retenir
Section intitulée « À retenir »podman buildutilise Buildah en interne, stockage partagé, sans démon- Pinner les versions :
python:3.12.8-alpine3.21paspython:latest - Cache = ordre des instructions : dépendances avant code
.containerignore: exclure.git/,.venv/,node_modules/- Multi-stage : séparer builder (avec gcc) et runtime (sans)
- Secrets : utiliser
--secret, jamais ARG/ENV - Publier : tag + push + conserver le digest
- Squash : cas particulier, pas une best practice universelle