
Table des matières
Lorsque j’ai commencé à utiliser Docker, mes plus grosses erreurs ne concernaient pas les commandes ou la configuration. Ce sont des décisions qui ont ensuite provoqué des problèmes de sécurité, des images gonflées et des heures de débogage. À cette époque, mon seul objectif était de faire fonctionner les conteneurs. Je n’ai pas pensé aux meilleures pratiques ni à la façon dont ces premiers choix affecteraient les performances et la sécurité à long terme.
Avec l’expérience, j’ai réalisé que Docker est plus qu’un outil de packaging ; c’est un flux de travail qui nécessite une planification minutieuse. Si la conteneurisation garantit des environnements cohérents et facilite le déploiement, elle introduit également des défis tels que des failles de sécurité, des problèmes de réseau et même des conflits avec les VPN.
Dans cet article, je partagerai les plus grosses erreurs que j’ai commises avec Docker et comment les corriger a augmenté ma productivité.
Choisir la mauvaise image de base
L’une des plus grandes leçons que j’ai apprises au début est que l’image de base que vous choisissez affecte tout, l’extraction, la construction, le déploiement, l’analyse et même le débogage. Au début, j’utilisais des images complètes du système d’exploitation comme « ubuntu:latest » simplement parce qu’elles me semblaient familières. Mais ces grandes images comportaient des coûts cachés : des constructions plus lentes, des déploiements plus lourds et des conteneurs finaux surdimensionnés.
Lorsque je suis passé à des images minimales et spécialement conçues telles que des images « Alpine », « Slim » ou spécifiques aux langues officielles, la différence a été immédiate. Mes images sont devenues plus petites, les builds se sont terminés plus rapidement et les analyses de sécurité ont montré moins de vulnérabilités.
Bien sûr, les images minimales ne sont pas toujours le bon choix ; certains projets ont réellement besoin des bibliothèques fournies avec Ubuntu ou Debian. Le véritable gain de productivité vient du choix intentionnel de votre image de base, et non par habitude. Choisissez l’image qui correspond aux besoins réels de votre projet et vous ressentirez une amélioration sur l’ensemble de votre flux de travail.
Secrets et informations d’identification codés en dur
Le codage en dur des valeurs de configuration a été l’une des plus grosses erreurs que j’ai commises au début. J’avais l’habitude de placer des éléments tels que les URL de base de données et les clés API directement dans le Dockerfile parce que cela me semblait pratique.
Mais cela signifiait que ces secrets étaient stockés dans l’image et finissaient par se retrouver dans le contrôle de version. Toute personne ayant accès à l’image ou au référentiel pourrait les voir, ce qui constitue un sérieux problème de sécurité.
Un moyen plus sûr consiste à garder le Dockerfile exempt d’informations sensibles et à transmettre les valeurs secrètes réelles uniquement lorsque le conteneur s’exécute. Par exemple, au lieu d’écrire des valeurs réelles dans le Dockerfile, vous définissez des variables d’environnement vides.
# Keep Dockerfile clean
ENV DATABASE_URL=""
ENV API_KEY=""Ensuite, vous fournissez les valeurs réelles au moment de l’exécution comme celle-ci.
docker run -e DATABASE_URL="postgres://user:pass@localhost:5432/appdb" -e API_KEY="my_real_key_here" myappCela permet de conserver les secrets en dehors de l’image, d’éviter de transmettre des données sensibles à Git et de faciliter la mise à jour des valeurs sans rien reconstruire.
Utiliser la dernière balise au lieu de versions spécifiques
Utiliser la dernière balise semble simple, mais cela conduit souvent à des versions imprévisibles. Un même Dockerfile peut se comporter différemment d’un jour à l’autre car l’image de base change tranquillement en arrière-plan. Par exemple, écrire FROM node:latest pourrait fonctionner aujourd’hui, mais demain, Docker pourrait extraire une version plus récente de Node, et votre build pourrait échouer sans aucune modification de votre part.
Les choses sont devenues beaucoup plus fluides lorsque j’ai commencé à utiliser des versions spécifiques comme celle-ci.
FROM node:20
FROM python:3.10Cela garantit des versions stables, facilite le débogage et évite les problèmes surprises causés par des mises à jour cachées. Cela vous fait également gagner du temps car vous savez toujours exactement sur quel environnement votre application s’exécute.
.dockerignore manquant ou mal configuré
Une erreur que j’ai commise au début a été de ne pas utiliser de fichier .dockerignore. Par défaut, Docker inclut l’intégralité de votre dossier de projet dans le contexte de construction, depuis « node_modules » et « .git » jusqu’aux fichiers temporaires et même les grands ensembles de données que vous avez oubliés. Cela peut ralentir les builds et rendre les images inutilement volumineuses.
Pour éviter de telles situations, créez un fichier « .dockerignore » et indiquez à Docker ce qu’il ne faut pas inclure. Il est recommandé de toujours ignorer les dossiers comme « .git », « node_modules », les journaux, les caches et les fichiers temporaires.

C’est un petit pas qui fait une grande différence.
Ordre des couches inefficace
Une autre erreur à éviter consiste à organiser vos instructions Dockerfile dans le mauvais ordre. Docker crée un nouveau calque pour chaque instruction. Si une première couche change, tout ce qui suit est reconstruit. Dans le passé, j’écrivais des Dockerfiles comme ceci.
# Poor layering. Any code change forces a full rebuild
FROM node:18-alpine
WORKDIR /app
COPY . .
RUN npm install
CMD ("npm", "start")Ici, le COPY .. est placé trop tôt. Même si je modifiais un seul fichier JavaScript, Docker devait réinstaller toutes les dépendances car le cache était invalidé. Cela a rendu mes builds inutilement lents.
Une meilleure approche consiste à séparer les dépendances du code de l’application afin que Docker puisse les mettre correctement en cache.
# Improved layering. Dependencies are cached separately
FROM node:18-alpine
WORKDIR /app
# Copy only the dependency files first
COPY package*.json ./
RUN npm install
# Copy the rest of the application afterward
COPY . .
CMD ("npm", "start")Pour optimiser encore davantage, vous pouvez regrouper les instructions en fonction de la fréquence à laquelle elles changent.
# System packages (hardly ever change)
RUN apk add --no-cache git bash
# App dependencies (usually change monthly)
COPY package*.json ./
RUN npm ci --only=production
# Application source code (changes frequently)
COPY . .En plaçant les couches les plus stables en premier et les couches qui changent fréquemment en dernier, Docker peut réutiliser les étapes mises en cache.
Tout regrouper en une seule étape
Lorsque j’ai commencé à utiliser Docker, je n’avais pas réalisé combien de poids j’ajoutais à mes images en mettant tout : les outils de développement, les compilateurs, les exécuteurs de tests et les artefacts de construction, dans un seul fichier Docker. J’ai expédié des images énormes, lentes à extraire et certainement peu adaptées à la production. La plupart de ces éléments n’étaient jamais censés aboutir en production, mais ils y sont restés simplement parce que j’ai tout construit en une seule étape.
Une fois que j’ai appris comment fonctionnent les constructions en plusieurs étapes, les choses ont changé instantanément. Je pouvais exécuter toutes les étapes lourdes en une seule étape, puis créer une image finale propre et minimale contenant uniquement ce dont l’application avait besoin pour s’exécuter. Cela a rendu mes images plus rapides à déployer, plus sécurisées et beaucoup plus petites.
Exécuter des conteneurs en tant que root
Au début, je ne pensais pas beaucoup à l’utilisateur sous lequel mon conteneur s’exécutait. Docker est par défaut root, donc je l’ai simplement suivi. Plus tard, j’ai réalisé que c’était une grave erreur. L’exécution en tant que root donne à un conteneur bien plus de contrôle que la plupart des applications n’en ont jamais besoin, et une petite mauvaise configuration peut exposer votre système à des risques inutiles.
Par exemple, le résultat suivant montre que le conteneur s’exécute en tant qu’utilisateur root, ce qui signifie qu’il dispose des privilèges de superutilisateur. Il peut modifier les zones sensibles du système, accéder aux périphériques du système et même interagir avec des groupes au niveau matériel, ce qui constitue un risque de sécurité sérieux pour tout environnement de production.

Une fois que j’ai compris cela, je suis passé à la création d’un utilisateur dédié à l’intérieur de l’image et à l’exécution de l’application via cet utilisateur au lieu de root.
# Create a safer user and group for the app
RUN addgroup -S webgroup && adduser -S webuser -G webgroup
# Copy project files and assign correct ownership
COPY --chown=webuser:webgroup . /app
# Run the container as the non-root user
USER webuserDe cette façon, l’utilisation d’un utilisateur non root rend le conteneur plus sûr, réduit les risques de privilèges et suit les meilleures pratiques de sécurité, sans ajouter de complexité.
Ne pas définir de limites de ressources
Sans limites, les conteneurs peuvent consommer toutes les ressources système, ralentissant ou faisant planter votre hôte. J’ai vécu cela lors d’une construction lourde ; un conteneur en fuite a tout stoppé.
Pour éviter cela, fixez toujours des limites de ressources afin que vos conteneurs restent dans des limites sûres. Vous pouvez le faire en utilisant des drapeaux comme --memory, --cpuset --memory-swap lors du démarrage d’un conteneur. Par exemple, la commande suivante limite le conteneur à 500 Mo de RAM et lui permet d’utiliser un seul cœur de processeur.
docker run --name my-app --memory="500m" --cpus="1.0" node:18-alpineUtilisation excessive du mode privilégié
Lorsque j’ai rencontré pour la première fois des problèmes avec les conteneurs Docker, j’ai pensé à utiliser --privileged était une solution rapide. C’était comme par magie, tout à coup, tout a fonctionné !
docker run --privileged my-containerMais j’ai vite réalisé que cela donnait au conteneur un accès presque illimité au système hôte. C’est un énorme risque pour la sécurité. Souvent, tout ce dont j’avais besoin était une petite capacité comme SYS_ADMINpas un accès privilégié complet.
docker run --cap-add=SYS_ADMIN my-containerEn utilisant --privileged était exagéré. Par conséquent, accorder uniquement les autorisations nécessaires garantit la sécurité de l’hôte tout en permettant au conteneur de fonctionner correctement.
Planifiez donc soigneusement votre configuration Docker dès le début. En évitant ces erreurs courantes, vos conteneurs seront plus sûrs, plus rapides et beaucoup plus faciles à maintenir, vous permettant ainsi de vous concentrer sur la création et le déploiement d’applications performantes au lieu de constamment résoudre les problèmes.







