<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Roméo DOSsOu</title>
    <description>The latest articles on DEV Community by Roméo DOSsOu (@romeo_dossou_b9a9ace7ba7c).</description>
    <link>https://dev.to/romeo_dossou_b9a9ace7ba7c</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3997351%2Ff9d06c16-4302-4ef2-808e-1fe5243f5c15.png</url>
      <title>DEV Community: Roméo DOSsOu</title>
      <link>https://dev.to/romeo_dossou_b9a9ace7ba7c</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/romeo_dossou_b9a9ace7ba7c"/>
    <language>en</language>
    <item>
      <title>J'ai écrit le Dockerfile que TOUT débutant écrit — puis je l'ai détruit erreur par erreur</title>
      <dc:creator>Roméo DOSsOu</dc:creator>
      <pubDate>Fri, 26 Jun 2026 11:25:18 +0000</pubDate>
      <link>https://dev.to/romeo_dossou_b9a9ace7ba7c/jai-ecrit-le-dockerfile-que-tout-debutant-ecrit-puis-je-lai-detruit-erreur-par-erreur-11eb</link>
      <guid>https://dev.to/romeo_dossou_b9a9ace7ba7c/jai-ecrit-le-dockerfile-que-tout-debutant-ecrit-puis-je-lai-detruit-erreur-par-erreur-11eb</guid>
      <description>&lt;h1&gt;
  
  
  J'ai écrit le Dockerfile que TOUT débutant écrit — puis je l'ai détruit erreur par erreur
&lt;/h1&gt;

&lt;p&gt;Il y a quelques semaines, j'ai écrit mon premier vrai Dockerfile "instinctif" — celui que n'importe qui produit en suivant juste sa logique, sans connaître les bonnes pratiques. Et il fonctionnait. L'application démarrait, je voyais ma page web, j'étais content.&lt;/p&gt;

&lt;p&gt;Sauf que ce Dockerfile pesait &lt;strong&gt;1,1 Go&lt;/strong&gt;, exposait une clé API en clair, et faisait tourner mon code avec les pleins pouvoirs administrateur. Sans le savoir.&lt;/p&gt;

&lt;p&gt;Cet article raconte comment je suis passé de ce Dockerfile naïf à une version professionnelle de &lt;strong&gt;90 Mo&lt;/strong&gt;, en corrigeant 7 erreurs une par une — avec, à chaque étape, la preuve concrète du problème avant la solution.&lt;/p&gt;

&lt;p&gt;Si tu n'as jamais touché Docker, tu peux suivre cet article sans aucun prérequis. Si tu en as déjà fait, tu vas probablement reconnaître au moins 3 de ces erreurs dans tes propres projets (je ne juge pas, je les ai toutes faites).&lt;/p&gt;

&lt;h2&gt;
  
  
  Le point de départ : le Dockerfile qu'on écrit tous
&lt;/h2&gt;

&lt;p&gt;Voici, mot pour mot, le genre de Dockerfile qu'on produit naturellement quand on découvre Docker :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:20&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; API_KEY=mon_super_secret_123&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", "app.js"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sept lignes. Ça marche. Et ça contient sept problèmes différents. Voyons-les un par un.&lt;/p&gt;

&lt;h2&gt;
  
  
  Erreur n°1 et 2 : l'ordre des instructions détruit le cache
&lt;/h2&gt;

&lt;p&gt;Docker construit une image en couches. Chaque ligne du Dockerfile = une couche. Et Docker met en cache les couches qui n'ont pas changé, pour ne pas refaire un travail inutile.&lt;/p&gt;

&lt;p&gt;Le souci avec &lt;code&gt;COPY . .&lt;/code&gt; placé avant &lt;code&gt;RUN npm install&lt;/code&gt; : si tu modifies &lt;strong&gt;un seul fichier&lt;/strong&gt; de ton code, Docker considère que la couche &lt;code&gt;COPY . .&lt;/code&gt; a changé — et il refait &lt;strong&gt;tout ce qui suit&lt;/strong&gt;, y compris &lt;code&gt;npm install&lt;/code&gt;, qui pourtant n'a rien à voir avec ton changement.&lt;/p&gt;

&lt;p&gt;Résultat concret : 25 à 30 secondes perdues à chaque rebuild, pour rien.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;La correction :&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:20&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="c"&gt;# On copie SEULEMENT les fichiers de dépendances d'abord&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package.json package-lock.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt;

&lt;span class="c"&gt;# Le code source, qui change souvent, vient APRÈS&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Après cette correction, modifier ton code source ne déclenche plus jamais un &lt;code&gt;npm install&lt;/code&gt; inutile. La ligne apparaît en cache (&lt;code&gt;CACHED&lt;/code&gt;) à chaque rebuild, tant que tu ne touches pas à &lt;code&gt;package.json&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;La règle à retenir&lt;/strong&gt; : dans un Dockerfile, on place ce qui change rarement en haut, et ce qui change souvent en bas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Erreur n°3 : pas de &lt;code&gt;.dockerignore&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Sans ce fichier, l'instruction &lt;code&gt;COPY . .&lt;/code&gt; copie absolument tout ce qui se trouve dans ton dossier de projet — y compris des choses que tu ne voudrais jamais voir dans une image : &lt;code&gt;node_modules&lt;/code&gt; (inutile, il est recréé par &lt;code&gt;npm install&lt;/code&gt;), &lt;code&gt;.git&lt;/code&gt; (tout ton historique de commits), et surtout un éventuel fichier &lt;code&gt;.env&lt;/code&gt; contenant tes mots de passe locaux.&lt;/p&gt;

&lt;p&gt;J'ai testé en créant un faux &lt;code&gt;.env&lt;/code&gt; avec un mot de passe factice, puis j'ai lancé :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; mon-app &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; /app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Le fichier &lt;code&gt;.env&lt;/code&gt; apparaissait dans la liste. Sans aucune protection.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;La correction&lt;/strong&gt;, un fichier &lt;code&gt;.dockerignore&lt;/code&gt; classique :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;node_modules/
.git/
.env
*.log
tests/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Même logique qu'un &lt;code&gt;.gitignore&lt;/code&gt;, mais pour Docker.&lt;/p&gt;

&lt;h2&gt;
  
  
  Erreur n°4 : la démonstration qui m'a le plus marqué — les secrets immuables
&lt;/h2&gt;

&lt;p&gt;Celle-ci vaut la peine d'être détaillée parce qu'elle m'a vraiment surpris.&lt;/p&gt;

&lt;p&gt;J'ai écrit ceci dans mon Dockerfile :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; API_KEY=mon_super_secret_123&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;unset &lt;/span&gt;API_KEY
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mon intuition de débutant : "j'ai supprimé la variable juste après, donc elle ne devrait plus être visible". Faux.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nb"&gt;history&lt;/span&gt; &lt;span class="nt"&gt;--no-trunc&lt;/span&gt; mon-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Le secret apparaît, en clair, dans l'historique des couches. Pour toujours. Parce que chaque couche Docker est &lt;strong&gt;immuable&lt;/strong&gt; — une fois écrite, elle ne disparaît jamais, même si une couche suivante "annule" son effet visible.&lt;/p&gt;

&lt;p&gt;C'est exactement le genre de vulnérabilité qu'on retrouve régulièrement dans des images publiées par erreur sur des registries publics, avec des clés AWS ou des tokens GitHub encore lisibles dans l'historique.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;La correction&lt;/strong&gt; : ne jamais écrire de valeur sensible dans le Dockerfile. On déclare la variable vide, et on injecte sa vraie valeur au moment du &lt;code&gt;docker run&lt;/code&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; API_KEY=""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--env-file&lt;/span&gt; .env.runtime mon-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Le fichier &lt;code&gt;.env.runtime&lt;/code&gt; ne quitte jamais ta machine — il n'est jamais copié dans l'image.&lt;/p&gt;

&lt;h2&gt;
  
  
  Erreur n°5 : tourner en root
&lt;/h2&gt;

&lt;p&gt;Par défaut, si tu ne précises rien, ton application tourne avec l'utilisateur &lt;code&gt;root&lt;/code&gt; — l'équivalent administrateur sous Linux. Si quelqu'un trouve une faille dans ton code et l'exploite, il obtient directement les pleins pouvoirs sur le conteneur.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;La correction&lt;/strong&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;RUN &lt;/span&gt;useradd &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; /bin/sh appuser
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; appuser:appuser /app
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; appuser&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Après ça, &lt;code&gt;docker run mon-app whoami&lt;/code&gt; répond &lt;code&gt;appuser&lt;/code&gt; au lieu de &lt;code&gt;root&lt;/code&gt;. C'est le principe du moindre privilège — un classique de la sécurité système, appliqué en deux lignes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Erreurs n°6 et 7 : une image dix fois trop lourde
&lt;/h2&gt;

&lt;p&gt;Mon image naïve pesait &lt;strong&gt;1,1 Go&lt;/strong&gt;. Pour comprendre pourquoi, il faut comprendre ce que contient réellement &lt;code&gt;node:20&lt;/code&gt; : un système Debian complet, avec des centaines d'outils dont mon application n'utilise jamais 1%.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Première correction&lt;/strong&gt;, changer l'image de base :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:20-alpine&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alpine est une distribution Linux minimaliste. Ce seul changement m'a fait passer de 1,1 Go à environ 180 Mo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deuxième correction&lt;/strong&gt;, le multi-stage build — la technique la plus puissante de tout ce projet. L'idée : séparer la phase de construction de l'application (qui a besoin de tous les outils) de la phase d'exécution (qui n'a besoin que du résultat final).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Stage 1 : on construit, avec tous les outils nécessaires&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:20-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package.json package-lock.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--omit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dev
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;

&lt;span class="c"&gt;# Stage 2 : on ne garde que le strict nécessaire&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:20-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;runner&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /app/node_modules ./node_modules&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /app/app.js ./app.js&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /app/package.json ./package.json&lt;/span&gt;
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; appuser&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", "app.js"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Résultat final : environ &lt;strong&gt;90 Mo&lt;/strong&gt;. Une réduction de 92% par rapport au point de départ.&lt;/p&gt;

&lt;h2&gt;
  
  
  Le bilan complet
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Erreur&lt;/th&gt;
&lt;th&gt;Risque&lt;/th&gt;
&lt;th&gt;Gain après correction&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Mauvais ordre des couches&lt;/td&gt;
&lt;td&gt;Rebuild lent à chaque changement&lt;/td&gt;
&lt;td&gt;30s → 0,1s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pas de &lt;code&gt;.dockerignore&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Fichiers sensibles copiés&lt;/td&gt;
&lt;td&gt;Image plus légère et sûre&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Secrets en dur&lt;/td&gt;
&lt;td&gt;Fuite de clés API permanente&lt;/td&gt;
&lt;td&gt;Zéro secret dans l'image&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exécution en root&lt;/td&gt;
&lt;td&gt;Accès total en cas de faille&lt;/td&gt;
&lt;td&gt;Utilisateur limité&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Image lourde&lt;/td&gt;
&lt;td&gt;1,1 Go pour rien&lt;/td&gt;
&lt;td&gt;-80% avec Alpine&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pas de multi-stage&lt;/td&gt;
&lt;td&gt;Outils de build embarqués&lt;/td&gt;
&lt;td&gt;-92% au total&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Ce que j'en retiens
&lt;/h2&gt;

&lt;p&gt;La leçon la plus importante de tout ce projet : &lt;strong&gt;un Dockerfile qui fonctionne et un Dockerfile qui est bien fait sont deux choses complètement différentes&lt;/strong&gt;. La première version naïve ne plante jamais — c'est justement ce qui la rend dangereuse, parce qu'on ne voit aucun signal d'alarme.&lt;/p&gt;

&lt;p&gt;J'ai documenté l'intégralité de ce projet, avec le code source testable des deux versions (avant/après) et un guide pas-à-pas niveau zéro absolu, dans ce dépôt GitHub :&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://github.com/Bordley/mon-app.git" rel="noopener noreferrer"&gt;https://github.com/Bordley/mon-app.git&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Si tu repères d'autres erreurs classiques que je n'ai pas couvertes, ou si tu veux discuter d'un point précis, les commentaires sont ouverts.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl1dyuqj9k87v8llgh32w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl1dyuqj9k87v8llgh32w.png" alt=" " width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>docker</category>
      <category>devops</category>
      <category>beginners</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>title: "Docker Volumes : pourquoi tes données disparaissent (et comment les protéger pour de bon)"</title>
      <dc:creator>Roméo DOSsOu</dc:creator>
      <pubDate>Thu, 25 Jun 2026 11:39:14 +0000</pubDate>
      <link>https://dev.to/romeo_dossou_b9a9ace7ba7c/title-docker-volumes-pourquoi-tes-donnees-disparaissent-et-comment-les-proteger-pour-de-bon-4bnh</link>
      <guid>https://dev.to/romeo_dossou_b9a9ace7ba7c/title-docker-volumes-pourquoi-tes-donnees-disparaissent-et-comment-les-proteger-pour-de-bon-4bnh</guid>
      <description>&lt;p&gt;published: true&lt;br&gt;
tags: docker, devops, mysql, beginners&lt;br&gt;
cover_image:&lt;br&gt;
canonical_url:&lt;/p&gt;
&lt;h1&gt;
  
  
  Docker Volumes : pourquoi tes données disparaissent (et comment les protéger pour de bon)
&lt;/h1&gt;
&lt;h2&gt;
  
  
  Le piège dans lequel je suis tombé (et toi aussi, probablement)
&lt;/h2&gt;

&lt;p&gt;Voici une expérience que je te conseille de faire toi-même, une seule fois, pour bien comprendre la leçon.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; mysql-demo &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;MYSQL_ROOT_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;secret mysql:8.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tu te connectes, tu crées une table &lt;code&gt;clients&lt;/code&gt;, tu insères 3 lignes. Tu vérifies : tout est là.&lt;/p&gt;

&lt;p&gt;Puis :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; mysql-demo
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; mysql-demo &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;MYSQL_ROOT_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;secret mysql:8.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tu te reconnectes :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;clients&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- ERROR 1146 (42S02): Table 'shop.clients' doesn't exist&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Tout a disparu.&lt;/strong&gt; Pas un bug. Pas une erreur de manipulation. C'est exactement comme ça que Docker est censé fonctionner — et c'est précisément pour ça que les &lt;strong&gt;volumes&lt;/strong&gt; existent.&lt;/p&gt;

&lt;p&gt;Dans cet article, je t'explique pourquoi ça arrive, et comment je l'ai résolu avec un projet complet simulant l'infrastructure d'une PME (base de données, fichiers clients, sessions).&lt;/p&gt;




&lt;h2&gt;
  
  
  Pourquoi Docker "oublie" tout par défaut
&lt;/h2&gt;

&lt;p&gt;Un container Docker repose sur un système de fichiers en couches, appelé &lt;strong&gt;OverlayFS&lt;/strong&gt;. Schématiquement :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────┐
│  COUCHE D'ÉCRITURE (container)  │ ← temporaire
├─────────────────────────────────┤
│  COUCHES DE L'IMAGE (lecture    │ ← permanentes
│  seule)                         │
└─────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;L'&lt;strong&gt;image&lt;/strong&gt; (lecture seule) contient le système et les binaires — elle ne change jamais. Quand tu lances un container, Docker ajoute une &lt;strong&gt;couche d'écriture&lt;/strong&gt; par-dessus, où vivent tous les fichiers créés ou modifiés pendant l'exécution.&lt;/p&gt;

&lt;p&gt;Le problème : &lt;code&gt;docker rm&lt;/code&gt; détruit cette couche d'écriture. Et comme rien n'a été écrit ailleurs, tout part avec elle.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;L'analogie qui m'a aidé à vraiment comprendre :&lt;/strong&gt; l'image est un livre imprimé. La couche d'écriture est un post-it collé dessus. Tu jettes le post-it → tes notes disparaissent, mais le livre reste intact. Le livre, ici, c'est l'image — jamais affectée par la suppression d'un container.&lt;/p&gt;




&lt;h2&gt;
  
  
  Les 3 solutions, et quand utiliser laquelle
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Named Volume — pour les données critiques
&lt;/h3&gt;

&lt;p&gt;Un named volume est un espace de stockage &lt;strong&gt;entièrement géré par Docker&lt;/strong&gt;. Tu lui donnes un nom, Docker s'occupe du reste.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mysql:8.0&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;db_data:/var/lib/mysql&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;db_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Quand l'utiliser :&lt;/strong&gt; toute base de données (MySQL, Postgres, MongoDB...). C'est la protection minimale non-négociable pour n'importe quelle donnée que tu ne peux pas te permettre de perdre.&lt;/p&gt;

&lt;p&gt;Preuve que ça fonctionne :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose down        &lt;span class="c"&gt;# supprime les containers, PAS le volume&lt;/span&gt;
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;       &lt;span class="c"&gt;# les données sont toujours là&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Bind Mount — pour les fichiers accessibles depuis le serveur
&lt;/h3&gt;

&lt;p&gt;Un bind mount connecte un dossier de ta machine hôte directement à un dossier du container. Les deux dossiers sont littéralement &lt;strong&gt;le même&lt;/strong&gt; dossier, vu depuis deux côtés différents.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx:alpine&lt;/span&gt;
    &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1000:1000"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./uploads:/usr/share/nginx/html/uploads&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Quand l'utiliser :&lt;/strong&gt; fichiers uploadés par les utilisateurs, fichiers de configuration, code source en développement.&lt;/p&gt;

&lt;p&gt;⚠️ &lt;strong&gt;Le piège classique :&lt;/strong&gt; un fichier créé depuis l'intérieur du container appartient à &lt;code&gt;root&lt;/code&gt; par défaut. Si ton script de déploiement tourne avec un utilisateur normal, il ne pourra pas le supprimer ou le modifier. La ligne &lt;code&gt;user: "1000:1000"&lt;/code&gt; (ton UID Linux, trouvable avec la commande &lt;code&gt;id&lt;/code&gt;) résout ce problème en alignant les permissions.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. tmpfs — pour les données sensibles et temporaires
&lt;/h3&gt;

&lt;p&gt;Un tmpfs est un espace de stockage qui vit &lt;strong&gt;exclusivement en RAM&lt;/strong&gt;. Il n'est jamais écrit sur disque, et il disparaît automatiquement à l'arrêt du container.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;tmpfs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/tmp/sessions:size=64m,mode=1777&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Quand l'utiliser :&lt;/strong&gt; tokens de session, secrets temporaires, cache haute performance non-critique.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pourquoi c'est important pour la sécurité :&lt;/strong&gt; si un token de session n'existe qu'en RAM, il ne peut pas être lu via un backup disque ou un accès physique malveillant au serveur. C'est le même principe utilisé par des outils comme HashiCorp Vault pour la gestion de secrets.&lt;/p&gt;




&lt;h2&gt;
  
  
  Le tableau de décision
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Besoin&lt;/th&gt;
&lt;th&gt;Type à utiliser&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Base de données&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Named Volume&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fichiers uploadés / configuration&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Bind Mount&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Code source en dev (hot-reload)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Bind Mount&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sessions / secrets temporaires&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;tmpfs&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cache rapide non-critique&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;tmpfs&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Et si le volume lui-même est supprimé ? (Backup &amp;amp; Restore)
&lt;/h2&gt;

&lt;p&gt;Un named volume protège contre la suppression du &lt;em&gt;container&lt;/em&gt;. Mais que se passe-t-il si le &lt;em&gt;volume&lt;/em&gt; lui-même est supprimé, ou si le serveur entier est perdu ?&lt;/p&gt;

&lt;p&gt;C'est là qu'intervient la sauvegarde. Comme Docker cache l'emplacement réel d'un named volume, on ne peut pas simplement faire un &lt;code&gt;cp&lt;/code&gt;. La technique standard consiste à utiliser un container Alpine temporaire qui sert de pont entre le volume et un dossier de backup :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; db_data:/data:ro &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;/backups:/backups &lt;span class="se"&gt;\&lt;/span&gt;
  alpine &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;tar &lt;/span&gt;czf /backups/db_backup_&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%Y%m%d_%H%M%S&lt;span class="si"&gt;)&lt;/span&gt;.tar.gz &lt;span class="nt"&gt;-C&lt;/span&gt; /data &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Et pour restaurer après une suppression totale du volume :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker volume create db_data

docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; db_data:/data &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;/backups:/backups:ro &lt;span class="se"&gt;\&lt;/span&gt;
  alpine &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;tar &lt;/span&gt;xzf /backups/db_backup_20260615.tar.gz &lt;span class="nt"&gt;-C&lt;/span&gt; /data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;J'ai testé cette procédure de bout en bout : suppression volontaire du volume, puis restauration complète. Toutes les données sont revenues, exactement comme avant le sinistre.&lt;/p&gt;




&lt;h2&gt;
  
  
  La stack finale
&lt;/h2&gt;

&lt;p&gt;Le projet complet assemble 3 services représentant une infrastructure réaliste de PME :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;App (nginx)&lt;/strong&gt; → Bind Mount pour les uploads + tmpfs pour les sessions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MySQL&lt;/strong&gt; → Named Volume pour la base de données&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redis&lt;/strong&gt; → Named Volume + tmpfs pour le cache&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Avec un healthcheck pour fiabiliser le démarrage, un utilisateur dédié non-root pour la base de données (principe du moindre privilège), et une politique de redémarrage automatique.&lt;/p&gt;

&lt;p&gt;Le code complet est disponible sur GitHub &lt;em&gt;(lien à ajouter)&lt;/em&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Ce que j'en retiens
&lt;/h2&gt;

&lt;p&gt;Comprendre les volumes Docker, ce n'est pas mémoriser trois commandes. C'est comprendre que &lt;strong&gt;le cycle de vie d'un container et le cycle de vie d'une donnée sont deux choses complètement différentes&lt;/strong&gt;. Un container est jetable par design — c'est même ce qui rend Docker puissant (scalabilité, mises à jour sans risque, reproductibilité). Une donnée, elle, ne l'est pas.&lt;/p&gt;

&lt;p&gt;Une fois ce modèle mental posé, le choix entre Named Volume, Bind Mount et tmpfs n'est plus une question de mémorisation — c'est une question de bon sens appliqué au bon contexte.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Je documente mon apprentissage du Cloud et du DevOps, avec un focus sur des projets réalistes plutôt que des tutoriels abstraits. Si ce genre de contenu t'intéresse, n'hésite pas à me suivre.&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  docker #devops #mysql #beginners #cloudcomputing #infrastructure #containerization #backend #learning #africatech
&lt;/h1&gt;

</description>
      <category>docker</category>
      <category>devops</category>
      <category>mysql</category>
      <category>cloudcomputing</category>
    </item>
    <item>
      <title># J'ai réduit mon image Docker Flask de 445MB à 35MB — voici exactement comment</title>
      <dc:creator>Roméo DOSsOu</dc:creator>
      <pubDate>Mon, 22 Jun 2026 17:19:28 +0000</pubDate>
      <link>https://dev.to/romeo_dossou_b9a9ace7ba7c/-jai-reduit-mon-image-docker-flask-de-445mb-a-35mb-voici-exactement-comment-32fg</link>
      <guid>https://dev.to/romeo_dossou_b9a9ace7ba7c/-jai-reduit-mon-image-docker-flask-de-445mb-a-35mb-voici-exactement-comment-32fg</guid>
      <description>&lt;p&gt;&lt;em&gt;Temps de lecture : 8 minutes — niveau débutant/intermédiaire&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Il y a quelques semaines, je dockerise une petite API Flask. Deux routes, 80 lignes de code. Je lance &lt;code&gt;docker build&lt;/code&gt;, j'attends, et quand je tape &lt;code&gt;docker images&lt;/code&gt; — &lt;strong&gt;445MB&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Quatre cent quarante-cinq mégaoctets. Pour une app qui tient dans un fichier.&lt;/p&gt;

&lt;p&gt;Je me suis dit : quelque chose ne va pas. Et j'ai décidé de comprendre &lt;em&gt;exactement&lt;/em&gt; d'où venait ce poids, et comment l'éliminer. Ce que j'ai trouvé a transformé ma façon d'écrire tous mes Dockerfiles depuis.&lt;/p&gt;

&lt;p&gt;Voici la méthode complète, chiffres à l'appui.&lt;/p&gt;




&lt;h2&gt;
  
  
  Le point de départ : le Dockerfile "débutant"
&lt;/h2&gt;

&lt;p&gt;Voici le Dockerfile que j'avais écrit — et que la majorité des tutoriels en ligne t'apprennent à écrire :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.11&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 5000&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["python", "app.py"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Propre. Lisible. Fonctionnel. Et catastrophiquement inutile en production.&lt;/p&gt;




&lt;h2&gt;
  
  
  Étape 1 : diagnostiquer avant de toucher quoi que ce soit
&lt;/h2&gt;

&lt;p&gt;Avant d'optimiser, j'ai fait la radiographie. La commande &lt;code&gt;docker history flask-fat&lt;/code&gt; affiche chaque couche de l'image avec son poids :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt    111MB
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .                               37kB&lt;/span&gt;
[ couches de python:3.11 ]             ~900MB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Le verdict est immédiat : &lt;strong&gt;mon code pèse 37 kilooctets&lt;/strong&gt;. Le reste — 99% du poids — c'est l'infrastructure que j'avais choisie sans y réfléchir.&lt;/p&gt;




&lt;h2&gt;
  
  
  Optimisation 1 : le &lt;code&gt;.dockerignore&lt;/code&gt; (quick win)
&lt;/h2&gt;

&lt;p&gt;Première chose facile : empêcher Docker de copier des fichiers inutiles.&lt;/p&gt;

&lt;p&gt;Sans &lt;code&gt;.dockerignore&lt;/code&gt;, &lt;code&gt;COPY . .&lt;/code&gt; embarque dans l'image : l'environnement virtuel Python local (&lt;code&gt;.venv/&lt;/code&gt; — souvent 200MB+), l'historique Git complet, les fichiers &lt;code&gt;.env&lt;/code&gt; avec les mots de passe...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;.&lt;span class="n"&gt;venv&lt;/span&gt;/
&lt;span class="err"&gt;__&lt;/span&gt;&lt;span class="n"&gt;pycache__&lt;/span&gt;/
.&lt;span class="n"&gt;git&lt;/span&gt;/
.&lt;span class="n"&gt;env&lt;/span&gt;
.&lt;span class="n"&gt;env&lt;/span&gt;.*
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;⚠️ Ce point dépasse la performance : un fichier &lt;code&gt;.env&lt;/code&gt; dans une image publiée sur Docker Hub, c'est tes clés API exposées à tout le monde. C'est l'une des fuites de secrets les plus fréquentes en entreprise.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Gain : marginal sur ce projet, critique en sécurité.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Optimisation 2 : changer l'image de base (-88%)
&lt;/h2&gt;

&lt;p&gt;C'est là que tout se joue. Il existe trois "tailles" d'images Python officielles :&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Image&lt;/th&gt;
&lt;th&gt;Ce qu'elle contient&lt;/th&gt;
&lt;th&gt;Poids&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;python:3.11&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Debian complet + Python + compilateurs + outils&lt;/td&gt;
&lt;td&gt;~900MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;python:3.11-slim&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Debian minimal + Python&lt;/td&gt;
&lt;td&gt;~130MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;python:3.11-alpine&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Alpine Linux + Python (rien d'autre)&lt;/td&gt;
&lt;td&gt;~50MB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Un seul mot à changer dans le Dockerfile :&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Avant&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.11&lt;/span&gt;

&lt;span class="c"&gt;# Après&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.11-alpine&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Résultat : &lt;strong&gt;445MB → 53MB&lt;/strong&gt;. Un changement d'une ligne pour -88%.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Le piège à connaître&lt;/strong&gt; : Alpine utilise une bibliothèque système différente de Debian (&lt;code&gt;musl libc&lt;/code&gt; vs &lt;code&gt;glibc&lt;/code&gt;). Certaines librairies Python avec des extensions C (&lt;code&gt;psycopg2&lt;/code&gt;, &lt;code&gt;Pillow&lt;/code&gt;, &lt;code&gt;cryptography&lt;/code&gt;) nécessitent des outils de compilation supplémentaires sur Alpine :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;RUN &lt;/span&gt;apk add &lt;span class="nt"&gt;--no-cache&lt;/span&gt; gcc musl-dev libffi-dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ce n'est pas compliqué — mais il faut le savoir avant de se retrouver avec des erreurs cryptiques.&lt;/p&gt;




&lt;h2&gt;
  
  
  Optimisation 3 : le multi-stage build
&lt;/h2&gt;

&lt;p&gt;Même avec Alpine, si tu installes des outils de compilation pour certaines librairies, ils restent dans l'image finale. C'est là qu'entre le &lt;strong&gt;multi-stage build&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;L'analogie que j'utilise : imagine une maison en construction. Les maçons arrivent avec échafaudages, bétonnières, perceuses. Quand la maison est prête, tu n'emménages pas avec tout ça — tu livres la maison propre.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Stage 1 : on construit ici, avec tous les outils nécessaires&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;python:3.11-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apk add &lt;span class="nt"&gt;--no-cache&lt;/span&gt; gcc musl-dev libffi-dev
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; requirements.txt .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-cache-dir&lt;/span&gt; &lt;span class="nt"&gt;--prefix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/install &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt

&lt;span class="c"&gt;# Stage 2 : image de production, table rase&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;python:3.11-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;runner&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="c"&gt;# On copie UNIQUEMENT les packages installés — pas gcc, pas musl-dev&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /install /usr/local&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 5000&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["gunicorn", "--bind", "0.0.0.0:5000", "app:app"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;La ligne magique : &lt;code&gt;COPY --from=builder /install /usr/local&lt;/code&gt;. Elle va chercher uniquement les packages Python compilés dans le stage précédent, sans les outils qui ont servi à les compiler.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gain : 53MB → 45MB&lt;/strong&gt; (modeste ici, mais 50-100MB sur des projets avec des dépendances C).&lt;/p&gt;




&lt;h2&gt;
  
  
  Optimisation 4 : épurer les dépendances
&lt;/h2&gt;

&lt;p&gt;Mon &lt;code&gt;requirements.txt&lt;/code&gt; de départ contenait &lt;code&gt;pytest&lt;/code&gt;, &lt;code&gt;black&lt;/code&gt;, &lt;code&gt;ipython&lt;/code&gt;. Ces outils ne servent qu'au développement. En production, ils ne s'exécutent jamais — mais ils pèsent, et surtout &lt;strong&gt;ils représentent des surfaces d'attaque potentielles&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;La solution : deux fichiers distincts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;requirements.txt&lt;/code&gt;&lt;/strong&gt; (production) :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;flask&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;=3.0.0&lt;/span&gt;
&lt;span class="py"&gt;gunicorn&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;=21.2.0&lt;/span&gt;
&lt;span class="py"&gt;Werkzeug&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;=3.0.1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;requirements-dev.txt&lt;/code&gt;&lt;/strong&gt; (développement local) :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="err"&gt;-r&lt;/span&gt; &lt;span class="err"&gt;requirements.txt&lt;/span&gt;
&lt;span class="py"&gt;pytest&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;=7.4.0&lt;/span&gt;
&lt;span class="py"&gt;black&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;=23.9.1&lt;/span&gt;
&lt;span class="py"&gt;ipython&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;=8.16.1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Le &lt;code&gt;-r requirements.txt&lt;/code&gt; dans le fichier dev signifie "installe d'abord les deps de prod". Un seul fichier à maintenir pour la prod, un seul pour le dev, et zéro duplication.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gain : 45MB → 35MB.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Le bilan final
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Technique&lt;/th&gt;
&lt;th&gt;Avant&lt;/th&gt;
&lt;th&gt;Après&lt;/th&gt;
&lt;th&gt;Gain&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.dockerignore&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;445MB&lt;/td&gt;
&lt;td&gt;441MB&lt;/td&gt;
&lt;td&gt;-1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Image Alpine&lt;/td&gt;
&lt;td&gt;441MB&lt;/td&gt;
&lt;td&gt;53MB&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-88%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-stage build&lt;/td&gt;
&lt;td&gt;53MB&lt;/td&gt;
&lt;td&gt;45MB&lt;/td&gt;
&lt;td&gt;-8%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dépendances épurées&lt;/td&gt;
&lt;td&gt;45MB&lt;/td&gt;
&lt;td&gt;35MB&lt;/td&gt;
&lt;td&gt;-8%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;445MB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;35MB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-92%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Les 3 règles que j'applique maintenant sur chaque projet
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Commencer par &lt;code&gt;-alpine&lt;/code&gt; ou &lt;code&gt;-slim&lt;/code&gt; par défaut&lt;/strong&gt;, et ne revenir à l'image complète que si une dépendance l'exige vraiment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Multi-stage build systématique&lt;/strong&gt; dès qu'une dépendance nécessite compilation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. &lt;code&gt;.dockerignore&lt;/code&gt; dans chaque projet dès le premier commit&lt;/strong&gt; — pas quand on pense à l'optimisation, dès le début.&lt;/p&gt;




&lt;h2&gt;
  
  
  Ce que ça change concrètement
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Déploiement plus rapide&lt;/strong&gt; : 35MB à transférer au lieu de 445MB. Sur une connexion lente ou un budget bande passante limité (context africain : pertinent), c'est significatif.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Moins de CVEs&lt;/strong&gt; : moins de packages = moins de surface d'attaque = moins de vulnérabilités détectées par les scanners de sécurité comme Trivy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coût réduit&lt;/strong&gt; : sur AWS ECR, Docker Hub, ou tout registry cloud, le stockage et le transfert d'images se facturent au mégaoctet.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Pour aller plus loin
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Le repo GitHub complet avec tous les Dockerfiles (naïf → optimisé) : &lt;a href="https://github.com/Bordley/flask-docker-optimization.git" rel="noopener noreferrer"&gt;https://github.com/Bordley/flask-docker-optimization.git&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Le tutoriel complet étape par étape (niveau zéro débutant) : [TUTORIAL.md dans le repo]&lt;/li&gt;
&lt;li&gt;Prochaine étape : scanner l'image avec &lt;strong&gt;Trivy&lt;/strong&gt; pour mesurer la réduction des CVEs&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Tu travailles sur un projet DevOps ou Cloud ? Je publie régulièrement des contenus techniques en français, avec un angle contexte africain (contraintes réseau, budget VPS, stack locale). Retrouve-moi sur [&lt;a href="http://www.linkedin.com/in/rom%C3%A9o-dossou3777" rel="noopener noreferrer"&gt;www.linkedin.com/in/roméo-dossou3777&lt;/a&gt;] et [Roméo Bordley Tech].&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>cloud</category>
      <category>docker</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
