Docker

30 questions en accès libre

Images, couches, build multi-étapes, volumes et réseaux : ce qu’on demande vraiment sur la conteneurisation.

Niveau

Cliquez sur une question pour dérouler la réponse attendue.

  1. 01Quelle différence entre une image et un conteneur ?Juniorimagesconteneursfondamentaux

    L’image est le modèle immuable. Le conteneur est une instance de cette image : une couche d’écriture, des namespaces, et un processus avec un cycle de vie.

    Lire la réponse détaillée
  2. 02Pourquoi l’ordre du Dockerfile change-t-il le cache ?Intermédiaireimagescouchescache

    Chaque instruction produit une couche. Dès qu’une instruction change, Docker invalide celle-ci et toutes les suivantes. L’ordre décide donc ce qui est recalculé.

    Lire la réponse détaillée
  3. 03Quand utilisez-vous COPY plutôt que ADD ?Juniordockerfileimages

    Presque toujours. COPY ne fait qu’une chose : copier des fichiers du contexte de build vers l’image. ADD en fait trois : copier, télécharger une URL, et extraire un archive local (tar, gzip, bzip2, xz). Cette magie est le problème.

    ADD https://exemple.com/app.tar.gz /opt/ télécharge pendant le build, sans checksum que vous contrôlez vraiment, et mélange réseau et couches. ADD app.tar.gz /opt/ décompresse sans que le lecteur du Dockerfile s’y attende. Deux sources de surprises, zéro bénéfice dans un build d’application.

    La règle qu’on attend : COPY par défaut. Si vous devez extraire un archive, un RUN tar le dit explicitement. Si vous devez récupérer un artefact, vous le faites dans le pipeline avant le build, ou avec un outil dont vous pinez la version — pas avec ADD.

    Ce que l’intervieweur vérifie

    Que vous avez déjà lu la documentation officielle et choisi le plus prévisible. Ce n’est pas une question de syntaxe. C’est : savez-vous qu’un Dockerfile se relit sous pression, et que chaque instruction « intelligente » est une instruction ambiguë.

    La mention utile : COPY --from d’un build multi-étapes n’a rien à voir avec ADD. C’est COPY, et c’est le bon outil.

  4. 04Quelle différence entre ENTRYPOINT et CMD ?Juniordockerfileruntime

    Les deux décrivent le processus PID 1, mais pas au même endroit. ENTRYPOINT est l’exécutable. CMD est l’argument par défaut. Concaténés, ils forment la commande réellement lancée.

    dockerfile
    ENTRYPOINT ["python", "app.py"]
    CMD ["--port", "8080"]

    docker run image lance python app.py --port 8080. docker run image --port 9090 remplace CMD, pas ENTRYPOINT : vous obtenez python app.py --port 9090. Pour remplacer l’exécutable, il faut --entrypoint.

    La forme exec (["cmd", "arg"]) est celle qu’on veut : le binaire est PID 1, les signaux (SIGTERM) lui arrivent. La forme shell (CMD python app.py) enveloppe tout dans /bin/sh -c. Votre application n’est plus PID 1 : un docker stop peut la tuer brutalement après le délai, sans lui laisser le temps de se terminer.

    Ce que l’intervieweur vérifie

    Que vous savez ce que docker run image args remplace. Beaucoup de candidats inversent les deux mots, ou croient que CMD est « la commande » et ENTRYPOINT « le hook ». La relance classique : « comment je passe un flag à ce conteneur sans reconstruire l’image ? » — en laissant CMD porter les défauts.

    Si vous ajoutez : je n’utilise la forme shell que si j’ai vraiment besoin d’un shell — variables expansées, pipes — vous montrez que vous avez déjà débogué un arrêt qui ne se terminait pas.

  5. 05Quelle différence entre ARG et ENV ?Juniordockerfileimages

    ARG vit au build. Il n’existe pas dans le conteneur lancé, sauf si vous le recopiez dans un ENV. Vous le passez avec docker build --build-arg VERSION=1.4. Un ARG déclaré avant le FROM sert uniquement à paramétrer l’image de base.

    ENV vit dans l’image. Il est visible au runtime (docker inspect, printenv dans le processus), hérité par chaque RUN suivant, et souvent surchargé par -e ou un fichier d’environnement Compose.

    dockerfile
    ARG APP_VERSION=0.0.0
    ENV APP_VERSION=$APP_VERSION

    Cette paire est légitime pour une version non secrète. Elle est un piège pour un mot de passe : l’ARG apparaît dans l’historique du build, l’ENV dans l’image poussée. Ni l’un ni l’autre n’est un coffre.

    Ce que l’intervieweur vérifie

    Que vous séparez temps de construction et temps d’exécution. La confusion produit deux défauts fréquents : une variable de runtime oubliée parce qu’elle n’était qu’un ARG, ou un secret cuit dans l’image « pour que ça marche partout ».

    La relance : « comment je change le niveau de log sans rebuild ? » — ENV avec une valeur par défaut, puis surcharge au docker run. Pas un ARG.

  6. 06À quoi sert vraiment un build multi-étapes ?Intermédiaireimagesmulti-etapes

    Séparer l’atelier et le produit : compiler avec tout l’outillage, ne livrer que le binaire et son runtime. Moins de surface, moins de poids, un cache de dépendances qui survit au code.

    Lire la réponse détaillée
  7. 07À quoi sert un fichier .dockerignore ?Juniordockerfilebuild

    À réduire le contexte de build : l’arbre de fichiers envoyé au démon (ou à BuildKit) avant même la première instruction. Sans lui, vous envoyez .git/, node_modules/, target/, des dumps, parfois un .env.

    Deux conséquences, toutes les deux visibles en entretien :

    1. Le temps. Un contexte de 1,4 Go pour copier trois fichiers source. Le build « ne démarre pas », en réalité l’envoi n’en finit pas.
    2. La fuite. Un COPY . . emporte ce que le ignore n’a pas écarté : clés, fichiers d’environnement, artefacts déjà compilés. Même sans COPY de ces chemins, un contexte énorme reste un coût et une habitude sale.

    Un ignore minimal, à adapter :

    text
    .git
    **/node_modules
    **/target
    **/.env*
    *.md

    Ce que l’intervieweur vérifie

    Que vous ne le présentez pas comme « un .gitignore pour Docker ». L’intention se ressemble ; la cible est le contexte, pas le dépôt. Un fichier versionné peut devoir être dans l’image ; un fichier secret ne doit être ni dans l’image ni dans le contexte.

    La phrase qui clôt : si ce n’est pas nécessaire au COPY, ça n’a rien à faire dans le contexte.

  8. 08Comment réduisez-vous la taille d’une image ?Intermédiaireimagesoptimisation

    Par couches, dans cet ordre — du plus rentable au plus cosmétique :

    1. Ne pas livrer l’outillage. Build multi-étapes : JDK et Maven d’un côté, JRE et jar de l’autre. C’est souvent un facteur quatre, pas dix pour cent.
    2. Choisir une base honnête. *-slim, JRE plutôt que JDK, distroless si vous assumez l’absence de shell. Alpine n’est pas gratuit : musl casse certains binaires glibc. Le citer sans cette réserve est un signal junior.
    3. Ignorer le bruit. .dockerignore : .git, caches, docs, binaires déjà construits.
    4. Nettoyer dans la même couche que l’install. apt-get update && apt-get install --no-install-recommends && rm -rf /var/lib/apt/lists/* dans un seul RUN. Une couche plus tard, les listes sont déjà gravées.
    5. Éviter les copies inutiles. Un COPY . . puis un RUN rm ne retire rien du poids de la couche précédente.

    La taille se lit avec docker images et, pour comprendre quoi, docker history ou un analyseur de couches (dive). Sans ça, on « optimise » au hasard.

    Ce que l’intervieweur vérifie

    Que vous avez une méthode, pas une religion Alpine. Il veut entendre qu’est-ce qui est nécessaire à l’exécution, puis la preuve (historique des couches). La recette Java est le cas d’école : 900 Mo à ~200 Mo sans changer une ligne métier.

  9. 09Pourquoi le processus ne doit-il pas rester root ?Intermédiairesecuritedockerfile

    Parce que root dans le conteneur est encore root pour tout ce que le processus touche : son système de fichiers, ses sockets, et — si un jour l’isolation lâche — trop de choses sur l’hôte. Une faille d’application en root devient une faille d’administration. En utilisateur dédié, elle reste une faille d’application.

    dockerfile
    RUN useradd --system --uid 10001 --create-home app
    USER app

    Les détails qui montrent que vous l’avez déjà fait :

    • Les fichiers copiés appartiennent à root. L’app user doit pouvoir lire ce qu’il exécute, et écrire uniquement là où c’est prévu (/tmp, un volume). Sinon vous « corrigez » en restant root.
    • Un port < 1024 pousse les gens à rester root. On écoute 8080 dans le conteneur, on mappe 80 sur l’hôte. Ou on donne NET_BIND_SERVICE, pas le UID 0.
    • Kubernetes : runAsNonRoot et un UID numérique dans l’image. Un USER app sans UID stable casse un cluster qui refuse les noms.

    Ce que l’intervieweur vérifie

    Que USER n’est pas une ligne décorative en bas du fichier. C’est une réduction de privilège, et elle impose de penser aux chemins en écriture. La mauvaise réponse : « de toute façon le conteneur est isolé ». L’isolation est une couche, pas une autorisation.

  10. 10Pourquoi un secret dans le Dockerfile reste dans l’image ?Seniorsecuriteimagescouches

    Chaque couche est un diff conservé. Un secret copié puis « effacé » dans un RUN suivant est toujours dans l’historique. docker history et un export le ressortent.

    Lire la réponse détaillée
  11. 11Pourquoi latest est-il un mauvais identifiant en production ?Intermédiaireimagesregistre

    Parce que latest n’est pas une version. C’est un pointeur mobile : le même nom peut désigner une autre image demain, sans que votre manifeste Kubernetes, votre Compose ou votre collègue n’ait rien changé. Deux hôtes qui font docker pull monapp:latest à cinq minutes d’intervalle n’ont aucune garantie d’avoir le même contenu.

    Un tag immuable (1.4.2, un SHA de commit) est déjà mieux, et encore imparfait : n’importe qui avec le droit de push peut retaguer 1.4.2. L’identifiant qui ne ment pas est le digest :

    text
    monapp@sha256:4f3c…   # contenu, pas un surnom

    En production on déploie le digest (ou un tag et le digest vérifié par la CI). Le tag reste pour les humains ; le digest est ce que l’orchestrateur tire.

    Ce que l’intervieweur vérifie

    Que vous reliez ça à la reproductibilité et à la chaîne d’approvisionnement, pas à une lubie de naming. La relance : « on a le même tag en recette et en prod, et prod ne se comporte pas comme recette » — très souvent, ce n’était pas la même image. docker image inspect sur le RepoDigests tranche en trente secondes.

  12. 12Quelle différence entre un volume et un bind mount ?Intermédiairevolumesstockage

    Les deux font survivre des fichiers au cycle de vie du conteneur. Ils ne sont pas administrés pareil.

    Un volume nommé est un objet Docker (docker volume create). Docker choisit l’emplacement sur l’hôte, gère les permissions de base, le laisse inspectable (docker volume ls) et le rend raisonnablement portable d’un moteur à l’autre. C’est le bon défaut pour une base, un dépôt d’uploads, un cache à conserver.

    Un bind mount colle un chemin de l’hôte (/home/dev/projet, C:\data) dans le conteneur. Idéal en développement : vous éditez sur la machine, le processus voit le fichier tout de suite. En production, c’est une dépendance à la topologie de l’hôte — chemins Windows contre Linux, SELinux, utilisateurs numériques qui ne correspondent pas.

    Le troisième, à citer : tmpfs, mémoire uniquement, rien sur disque. Utile pour un secret dépaqueté ou un cache sensible.

    Ce qu’un volume n’est pas : une sauvegarde. Personne ne le versionne pour vous. Et monter le code de production en bind « pour patcher vite » reconstitue le marche sur ma machine.

    Ce que l’intervieweur vérifie

    Que vous choisissez selon qui possède le chemin. L’application possède un volume. Le développeur possède un bind. Confondre les deux, c’est soit un service qui écrit dans une couche éphémère et « perd ses données au redémarrage », soit un déploiement qui suppose que /opt/app existe sur chaque nœud.

  13. 13Quand utilisez-vous bridge, host ou none ?Intermédiairereseaux

    bridge est le défaut, et le bon défaut. Le conteneur a son empilement réseau, Docker fait du NAT vers l’hôte. Sur le bridge par défaut, les conteneurs se joignent par IP, pas par nom. Sur un bridge défini par l’utilisateur (docker network create ou Compose), la résolution DNS interne fonctionne : http://api:8080. C’est la distinction que beaucoup omettent.

    host partage la pile réseau de l’hôte. Plus de -p : écouter 8080 dans le processus, c’est occuper 8080 sur la machine. Moins d’isolement, parfois moins de latence, des collisions de ports garanties dès que deux conteneurs veulent le même. On le réserve à un besoin mesuré (observabilité bas niveau, un démon qui doit voir toutes les interfaces), pas à « j’en avais marre du mapping ».

    none : loopback seulement. Pas de sortie, pas d’entrée. Batch isolé, tâche qui ne doit parler à personne, ou étape où vous ajoutez vous-même une interface. Rare, et c’est précisément pour ça qu’on le nomme : pour montrer que « pas de réseau » est un choix.

    Ce que l’intervieweur vérifie

    Que bridge n’est pas un seul objet dans votre tête. Bridge par défaut versus réseau nommé, et le DNS qui va avec, est le vrai test junior-plus. La relance host : « deux conteneurs en host qui écoutent 80 » — l’un des deux ne démarre pas, ou pire, celui qui a gagné le bind.

  14. 14Que règle Compose, et que ne règle-t-il pas ?Intermédiairecomposeorchestration

    Compose décrit un graphe de conteneurs sur une machine : réseaux, volumes, variables, démarrage. Il ne remplace pas un orchestrateur dès qu’il faut plusieurs nœuds ou une réparation.

    Lire la réponse détaillée
  15. 15Que vérifie vraiment un HEALTHCHECK ?Intermédiaireruntimesondes

    Que le processus répond à une sonde que vous avez écrite — pas qu’il existe. Un conteneur « Up » a un PID 1. Un conteneur healthy a, à intervalle régulier, réussi un CMD (souvent curl sur /health ou un pg_isready).

    dockerfile
    HEALTHCHECK --interval=10s --timeout=3s --retries=3 --start-period=20s \
      CMD wget -qO- http://127.0.0.1:8080/health || exit 1

    Trois états : starting (pendant --start-period), healthy, unhealthy. Compose peut attendre service_healthy. Une politique on-failure ne redémarre pas un processus encore vivant mais maladif : le HEALTHCHECK ne tue pas tout seul. Il étiquette. C’est un orchestrateur, ou un humain, qui décide de la suite.

    Ce que la sonde doit tester : une dépendance critique du point de vue de ce processus. Un /health qui renvoie 200 alors que la base est down ment. Un /health trop profond (trois API + le disque) crée des redémarrages en cascade.

    Ce que l’intervieweur vérifie

    Que vous séparez vivant, prêt, et capable de travailler. Docker n’a qu’un HEALTHCHECK ; Kubernetes a liveness, readiness, startup. En entretien Docker, on attend que vous sachiez que « le process tourne » n’est pas un critère métier — et que depends_on sans cette sonde est un mensonge poli.

  16. 16Pourquoi limiter le CPU et la mémoire d’un conteneur ?Intermédiaireruntimeressources

    Parce que sans plafond, un seul processus peut manger l’hôte. Fuite mémoire, requête pathologique, fork bomb involontaire : le voisin — et le démon Docker lui-même — suffoque. Les cgroups existent pour ça ; encore faut-il les régler.

    --memory (et l’équivalent Compose mem_limit) fixe un plafond. Au-delà, le noyau tue (OOM killer). Ce n’est pas une exception Java élégante : c’est un SIGKILL. --memory-swap mal compris (égal à la RAM, ou illimité selon les versions et l’hôte) change le moment où ça arrive, pas le principe.

    Le CPU est plus souple : --cpus (quota) borne l’usage moyen ; les shares ne s’expriment qu’en cas de contention. Un service « lent » n’est pas forcément limité : il peut simplement n’avoir jamais eu plus d’un demi-cœur, et vous le découvrez le jour où l’hôte est chargé.

    Côté JVM / Node / Go, le processus ne voit pas toujours la limite comme sa capacité. Une JVM qui croit avoir 16 Go d’hôte et un cgroup à 512 Mo se fait tuer. Il faut aligner -Xmx (ou les options cgroup-aware) sur la limite.

    Ce que l’intervieweur vérifie

    Que vous parlez d’isolation de voisins et d’OOM, pas de « performance ». La relance : un conteneur qui « disparaît » sans stack trace — allez voir dmesg / les événements OOM, pas seulement docker logs. Et que limiter n’est utile que si la limite est cohérente avec ce que le runtime croit avoir.

  17. 17Où allez-vous chercher les logs d’un conteneur ?Juniorruntimeobservabilite

    Sur la sortie standard et l’erreur standard du PID 1. docker logs <conteneur> lit ce que le logging driver a collecté (souvent json-file sur le poste, parfois journald ou un driver distant).

    La règle à énoncer : l’application écrit sur stdout/stderr, pas dans un fichier oublié dans la couche. Un log dans /var/log/app.log disparaît avec le conteneur, n’apparaît pas dans docker logs, et remplit le disque du writable layer. En production, un agent (Loki, Fluent Bit, le driver) prend stdout ; ce n’est plus votre problème de rotation locale — encore faut-il que le driver ait une rotation (max-size, max-file sur json-file), sinon c’est l’hôte qui sature.

    Pour un processus déjà mort : docker logs marche encore tant que le conteneur n’est pas rm. docker inspect donne le code de sortie. Si vous avez fait docker rm -f trop vite, vous avez jeté la scène.

    Ce que l’intervieweur vérifie

    Le réflexe 12-factor, pas la mémorisation des drivers. Si vous ajoutez : je ne fais pas docker exec pour ouvrir un fichier de log en première intention — vous avez déjà débogué un conteneur qui meurt au démarrage.

  18. 18Quelle politique de redémarrage choisissez-vous ?Juniorruntime

    Celle qui correspond à qui a le droit d’arrêter le service, pas « always parce que c’est plus sûr ».

    • no : défaut. Le processus meurt, il reste mort. Correct pour un job one-shot et pour le debug.
    • on-failure[:n] : redémarre seulement si le code de sortie n’est pas 0. Un exit 0 volontaire (migration finie, tâche cron) ne boucle pas.
    • always : redémarre quoi qu’il arrive, y compris après un stop, quand le démon Docker redémarre. Au reboot de l’hôte, le conteneur revient même si vous l’aviez arrêté à la main hier.
    • unless-stopped : comme always, sauf si vous l’avez arrêté explicitement. C’est souvent le bon défaut d’un service long sur une VM.

    Aucune de ces politiques ne remplace une sonde. Un processus zombie qui « tourne » sans répondre reste Up. Et un crash en boucle (always + binaire cassé) est un ventilateur de logs, pas de la haute disponibilité.

    Ce que l’intervieweur vérifie

    Que vous connaissez la différence always / unless-stopped au redémarrage du démon. C’est la question piège. La phrase de clôture : la politique décide quoi faire d’un exit ; elle ne décide pas si le service est sain.

  19. 19Quand utilisez-vous docker exec plutôt que attach ?Juniorruntimedebug

    docker attach se branche sur le stdio du PID 1 : ce que le processus principal lit et écrit. Utile pour une appli vraiment interactive que vous avez lancée au premier plan. Dangereux le jour où vous faites Ctrl+C : selon les flags (-t, --sig-proxy), vous envoyez un signal au processus principal et vous tuez le conteneur.

    docker exec lance un autre processus dans les mêmes namespaces (fichiers, réseau, PID). docker exec -it web sh ouvre un shell à côté de l’application. L’application n’est pas le shell. Quitter le shell ne quitte pas le service.

    En entretien, le réflexe sain : exec pour inspecter, attach presque jamais sur un service. Et exec n’est pas une porte d’exploitation permanente : pas de shell dans une image distroless, et ce n’est pas un mal.

    Ce que l’intervieweur vérifie

    Que vous savez quel processus reçoit vos touches. La confusion attach/exec est le classique qui produit un incident de prod (« j’étais juste connecté »). Si vous mentionnez exec en lecture (ls, wget localhost) plutôt qu’installer des paquets dans le conteneur vivant, vous montrez que vous ne réparez pas une image à la main.

  20. 20Que fait vraiment un mapping de ports ?Intermédiairereseauxruntime

    Il publie un port du conteneur sur une adresse de l’hôte. -p 8080:80 signifie : depuis l’hôte (et souvent depuis le LAN), hôte:8080 est NATé vers le port 80 dans le réseau du conteneur. Le processus, lui, écoute 80 à l’intérieur. Inverser les deux nombres est l’erreur de début de stage.

    EXPOSE 80 ne publie rien. C’est de la documentation, et un défaut pour -P (port hôte aléatoire). Beaucoup de Dockerfile exposent un port que personne n’a mappé : le service parle en interne, pas depuis votre navigateur.

    Le bind compte. -p 127.0.0.1:8080:8080 n’écoute que sur la loopback de l’hôte. -p 8080:8080 écoute en général sur 0.0.0.0 : tout le monde sur le réseau de la machine. Sur un laptop de café, ce n’est pas le même risque.

    En network_mode: host, le mapping n’a plus de sens : il n’y a plus de NAT. En bridge nommé, les conteneurs se parlent par nom sans -p ; le mapping est pour le monde extérieur.

    Ce que l’intervieweur vérifie

    Que vous séparez écouter, exposer et publier. La relance : « ça marche en exec curl localhost et pas depuis mon navigateur » — presque toujours un -p manquant, un bind hôte sur 127.0.0.1, ou un process qui n’écoute que 127.0.0.1 dans le conteneur. Dans ce dernier cas le mapping ne sauve rien : Docker NAT vers l’interface du conteneur, pas vers sa loopback, et le processus refuse le paquet. C’est une heure de perdue une fois ; on la raconte.

  21. 21Quel intérêt, et quelles limites, d’une image distroless ?Seniorimagessecurite

    L’intérêt : il n’y a presque plus d’OS. Pas de gestionnaire de paquets, souvent pas de shell, une surface CVE réduite à la libc (ou à rien) et à votre runtime. scratch va plus loin : image vide, réservée aux binaires vraiment autonomes (Go, Rust liés statiquement). Distroless (Debian ou autre, dépouillé) garde juste assez pour une JVM ou un interpréteur.

    Les limites, à citer sans qu’on vous les tire :

    • Vous ne docker exec plus un bash. Le debug passe par les logs, un sidecar, une variante -debug de la même image, ou un ephemeral container côté Kubernetes. Ce n’est pas un inconvénient : c’est le contrat.
    • Le binaire doit correspondre à la libc. Un Go en CGO_ENABLED=1 lié à glibc ne démarre pas sur scratch. Un Alpine (musl) n’est pas un Distroless Debian.
    • Les certificats, le fuseau, le user. Distroless les fournit souvent ; scratch non. Oublier ca-certificates est le classique HTTPS qui « marche en local » sur une image grasse.
    • Les shells dans un HEALTHCHECK (CMD-SHELL) tombent. Il faut un binaire présent, ou déplacer la sonde hors de l’image.

    Ce que l’intervieweur vérifie

    Que vous ne récitez pas « Distroless c’est plus sécure » comme un autocollant. Vous savez ce que vous perdez (debug, outils, parfois glibc) et ce que vous gagnez (moins de paquets à patcher). La phrase senior : on choisit Distroless quand le pipeline de debug existe déjà ; on ne l’impose pas pour cacher un build qu’on ne sait pas lire.

  22. 22Qu’apporte BuildKit par rapport au builder classique ?Intermédiairebuildcache

    BuildKit est le moteur de build moderne (le défaut depuis Docker 23). Le builder historique enchaînait les instructions en série, avec un cache de couches assez naïf. BuildKit ajoute ce qui change vraiment un pipeline :

    • Parallélisme. Deux étapes FROM indépendantes se construisent en même temps.
    • Montages de cache. --mount=type=cache,target=/root/.m2 garde le dépôt Maven hors des couches, d’un build à l’autre, sans le livrer dans l’image.
    • Secrets et SSH. --mount=type=secret et type=ssh : le jeton n’entre pas dans l’historique. C’est la réponse propre à un secret dans les couches.
    • Contexte paresseux. Il n’envoie pas tout le répertoire si rien ne s’en sert — encore mieux avec un dockerignore.
    • Cache distant. Un registre peut servir de cache CI : le runner éphémère n’est plus nu à chaque job.
    dockerfile
    # syntax=docker/dockerfile:1
    RUN --mount=type=cache,target=/root/.m2 mvn -B package

    Sans la première ligne # syntax=, certaines syntaxes de montage ne sont pas garanties.

    Ce que l’intervieweur vérifie

    Que vous ne dites pas « BuildKit, c’est plus rapide » et vous arrêtez. La vitesse vient de décisions (cache mounts, étapes parallèles), pas d’un bouton. La relance : un cache mount n’invalide pas comme une couche — c’est un disque partagé. Utile, et dangereux si vous y laissez un artefact corrompu que plus personne ne reconstruit.

  23. 23Pourquoi monter docker.sock est-il dangereux ?Seniorsecuriteruntime

    Le socket parle à l’API du démon. Qui le tient peut lancer un conteneur privilégié, monter le disque de l’hôte, et en pratique devenir root sur la machine.

    Lire la réponse détaillée
  24. 24Que faites-vous d’un CVE trouvé dans une image ?Seniorsecuriteimages

    Vous ne « patchez pas Docker ». Vous triez, puis vous reconstruisez.

    Un scan (Trivy, Grype, le scanner du registre) liste des CVE dans les paquets de la base, dans vos dépendances applicatives, parfois dans un binaire copié. La majorité n’est pas exploitable dans votre surface : une lib de CLI absente du runtime, un paquet que le process n’ouvre jamais, un vecteur local alors que le conteneur n’a pas de shell. La minorité l’est : une lib HTTP dans le PID 1, une crypto utilisée au démarrage.

    L’ordre de travail, celui qu’on attend :

    1. Où vit le paquet ? Base (FROM), étage builder oublié dans l’image finale, ou vos deps (package-lock, pom.xml). Le remède n’est pas le même.
    2. Est-il atteignable ? Version corrigée publiée ? Fix dans une image de base plus récente ? Ou CVE sans patch, à accepter avec une date de revue ?
    3. Reconstruire, retaguer, redéployer. On ne « apt-get upgrade » pas un conteneur vivant pour s’en vanter. L’image suivante, de préférence pineée par digest, remplace la précédente.
    4. Mesure d’attente si le correctif n’existe pas : retirer le paquet (distroless, multi-étapes), fermer le capabilité, limiter le réseau. Documenter le risque. Ne pas le cacher sous un badge vert.

    Un scan qui échoue la CI sur tout Critical sans tri produit des exceptions silencieuses au bout d’un mois. Un scan informatif sans seuil ne produit rien.

    Ce que l’intervieweur vérifie

    Que vous avez un processus, pas une peur. La mauvaise réponse : « on bloque le merge dès qu’il y a un CVE ». L’autre mauvaise : « on scanne une fois par an ». La bonne cite la provenance du paquet et le fait qu’une image n’est saine que jusqu’au prochain advisory — d’où des rebuilds de base réguliers, même sans commit métier.

  25. 25En quoi un conteneur n’est-il pas une machine virtuelle ?Intermédiairefondamentauxconteneurs

    Une machine virtuelle emporte un noyau invité. L’hyperviseur simule du matériel ; l’OS à l’intérieur croit avoir une machine. Isolation forte, démarrage lent, densité faible, patch de noyau par VM.

    Un conteneur emprunte le noyau de l’hôte. Ce que Docker (via runc, les cgroups, les namespaces) isole, ce sont des vues : processus, réseau, montage, utilisateurs. Le filesystem de l’image n’est pas un disque de VM : c’est un assemblage de couches. Démarrage en secondes, dizaines ou centaines d’instances sur la même machine, un seul noyau à attaquer.

    D’où les conséquences qu’on veut entendre :

    • Un module noyau, un CVE noyau, un CAP_SYS_ADMIN mal donné : le rayon est l’hôte, pas « cette VM ».
    • Vous ne « changez pas de kernel » dans un Dockerfile. FROM ubuntu n’installe pas un noyau Ubuntu ; il installe userland Ubuntu sur le noyau de l’hôte.
    • Windows contre Linux : un conteneur Linux attend un noyau Linux (ou une VM utilitaire, comme Docker Desktop). Ce n’est pas magique.

    Ce que l’intervieweur vérifie

    Le mot noyau partagé. Tout le reste — « plus léger », « plus rapide » — en découle et ne suffit pas. Si vous ajoutez qu’un conteneur n’est pas une frontière de sécurité équivalente à une VM, vous évitez le discours marketing que le recruteur a déjà entendu dix fois ce matin-là.

  26. 26Docker ou Kubernetes : qui fait quoi ?Intermédiairefondamentauxorchestration

    Docker fabrique et peut faire tourner l’image. Kubernetes place des réplicas, les soigne, leur donne une adresse stable. Ils s’enchaînent ; ils ne se concurrencent pas.

    Lire la réponse détaillée
  27. 27Pourquoi une image marche chez moi et échoue ailleurs ?Intermédiaireimagesreproductibilite

    Parce que ce n’était souvent pas la même image, ou pas le même contrat autour. « Ça marche chez moi » décrit un poste, pas un artefact.

    Les causes, dans l’ordre où je les cherche :

    1. Le tag a bougé. latest ou dev n’est pas le même digest sur les deux machines. docker image inspect / le digest du registre tranche. C’est latest contre digest.
    2. L’architecture. Image amd64 sur un Mac ARM, ou l’inverse, avec de l’émulation qui passe en local et explose en CI x86 — ou un binaire natif absent.
    3. Ce qui n’est pas dans l’image. Un bind mount de votre $HOME/.aws, un .env chargé par Compose et absent du pipeline, un secret dans votre shell. Ailleurs, le fichier n’est pas là ; le process meurt ou parle à la mauvaise API.
    4. Le réseau de l’hôte. network_mode: host, localhost qui désigne votre laptop, un VPN. Dans le pipeline, localhost est le runner.
    5. Les ressources et le user. Limite mémoire plus basse en CI, USER qui ne peut pas écrire un chemin que vous montiez en local en root.

    La méthode : comparer digest, plateforme, variables, montages, commande. Pas relancer en boucle en priant.

    Ce que l’intervieweur vérifie

    Que votre premier réflexe n’est pas « Docker est non déterministe ». Docker est déterministe à image et à contrat égaux. C’est le poste qui ne l’est pas. Si vous proposez de reproduire avec le même docker run que la CI (mêmes flags, même digest), vous avez la discipline.

  28. 28Un conteneur meurt au démarrage : par où commencez-vous ?Intermédiairedebugruntime

    On lit le code de sortie et les logs du PID 1 avant d’ouvrir un shell. La plupart des morts au boot sont une commande fausse, un fichier absent, ou un process qui n’est pas prévu pour être PID 1.

    Lire la réponse détaillée
  29. 29Que faites-vous d’un cache de build cassé ou trop agressif ?Seniorcachebuild

    Je sépare les deux symptômes. Ils se soignent à l’opposé.

    Cache cassé (tout rebuild à chaque fois). Je lis quelle instruction est le premier miss : docker build --progress=plain. Le coupable habituel est un COPY . . trop haut, un .dockerignore absent (le .git change à chaque commit), un ARG déclaré tôt et passé systématiquement (un timestamp, un SHA) qui invalide toute la suite, ou une CI sans cache (--no-cache copié-collé, runner sans BuildKit cache export). Le remède : réordonner le Dockerfile, ignorer le bruit, descendre les ARG volatils sous les couches stables, brancher un cache de registre.

    Cache trop agressif (l’image « marche » avec d’anciennes deps). npm ci réutilisé alors que le lock a été mal copié ; un RUN apt-get update datant de trois semaines ; un cache mount BuildKit qui sert un ~/.m2 corrompu ou incomplet ; une base FROM ubuntu:latest que personne n’a re-tirée. Le remède n’est pas --no-cache en permanence — ça masque le Dockerfile mal écrit. C’est : hasher ce qui doit invalider (le lockfile dans le COPY), piner les bases, et, pour les index apt, accepter une invalidation contrôlée (un ARG CACHE_BUST daté en CI nocturne, pas à chaque PR).

    dockerfile
    # L’ARG volatil après les couches de deps, pas avant.
    COPY package-lock.json ./
    RUN npm ci
    ARG VCS_REF
    LABEL org.opencontainers.image.revision=$VCS_REF

    Ce que l’intervieweur vérifie

    Que vous diagnostiquez avant de couper le cache. --no-cache est un rustine de pipeline, pas une architecture. La phrase senior : le cache est correct s’il rate quand l’entrée métier change, et seulement alors. Si vous nommez BuildKit (--cache-from, cache mounts) sans accuser le moteur au premier miss, vous avez déjà tenu une CI.

  30. 30À quoi ressemble un Dockerfile propre en entretien ?Seniordockerfileimages

    Une base pinée, un ignore, des couches ordonnées, un multi-étapes, un USER non root, aucun secret en couche, un process en forme exec. Le reste est du confort.

    Lire la réponse détaillée