« Ça marche chez moi » reste l’une des phrases les plus coûteuses d’une équipe de
développement web : versions de Node ou de PHP différentes d’un poste à l’autre, extension
manquante, variable d’environnement oubliée. La spécification Development Containers, portée
notamment par Microsoft et désormais implémentée par plusieurs éditeurs d’outils, répond à ce
problème en décrivant l’environnement de développement complet — runtime, outils, extensions,
ports — dans un fichier versionné avec le code.
Le problème que règlent les DevContainers
Un projet web réunit généralement une liste de dépendances précises : une version de
runtime (Node, PHP, Python), des outils en ligne de commande, parfois une base de données
locale, des extensions d’éditeur partagées pour le linting ou le formatage. Documenter cette
liste dans un README fonctionne un temps, jusqu’à ce que le document diverge de
la réalité. Les outils en ligne de commande propres à chaque projet
finissent alors par varier d’un poste à l’autre sans que personne ne s’en aperçoive avant un
bug difficile à reproduire.
Un DevContainer déplace cette documentation d’un texte à lire vers une configuration que
l’éditeur exécute directement : il construit un conteneur, y installe les outils prévus, y
ouvre le dossier du projet, et expose les ports nécessaires. Le nouvel arrivant dans l’équipe
n’installe plus rien sur sa machine hôte, hormis Docker et son éditeur.
Anatomie d’un devcontainer.json
La configuration vit dans .devcontainer/devcontainer.json, à la racine du
dépôt. Un exemple pour un projet Node.js avec PostgreSQL :
{
"name": "site-web-node",
"dockerComposeFile": "docker-compose.yml",
"service": "app",
"workspaceFolder": "/workspace",
"features": {
"ghcr.io/devcontainers/features/node:1": { "version": "22" }
},
"customizations": {
"vscode": {
"extensions": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode"
],
"settings": {
"editor.formatOnSave": true
}
}
},
"forwardPorts": [3000, 5432],
"postCreateCommand": "npm install"
}
Les « features » sont des briques réutilisables qui installent un runtime ou un
outil (Node, PHP, Docker-in-Docker, GitHub CLI) sans écrire de Dockerfile à la
main. Le bloc customizations.vscode installe automatiquement les extensions
d’équipe et leurs réglages, pour que le linting et le formatage soient identiques pour tout le
monde dès l’ouverture du projet.
Quels outils supportent la spécification
| Outil | Support | Remarque |
|---|---|---|
| Visual Studio Code | Natif (extension Dev Containers) | Référence historique de la spécification |
| GitHub Codespaces | Natif | Lit directement devcontainer.json pour provisionner le cloud |
| JetBrains (IntelliJ, PhpStorm, WebStorm) | Natif depuis 2024 | Mêmes fichiers, sans conversion |
CLI devcontainer | Natif | Construit et exécute le conteneur hors éditeur, utile en CI |
| Docker Compose seul | Compatible en complément | Le DevContainer peut s’appuyer sur un docker-compose.yml existant |
Mettre en place un DevContainer sur un projet existant
Sur un projet qui tourne déjà avec Docker Compose, la migration tient en trois étapes :
ajouter le dossier .devcontainer, y référencer le fichier compose existant plutôt
que d’en écrire un nouveau, puis déclarer les ports et la commande post-création. Pour une
équipe qui jongle entre plusieurs branches avec
des worktrees Git en parallèle,
chaque worktree peut ouvrir son propre DevContainer isolé, avec ses propres ports et son propre
état de base de données, sans interférer avec les autres copies de travail.
Les secrets ne doivent jamais être écrits dans devcontainer.json, versionné
avec le reste du dépôt. Ils sont transmis au conteneur via les variables d’environnement de
l’hôte (champ remoteEnv ou containerEnv pointant vers une variable
non versionnée), selon la même logique que pour
la gestion propre des variables d’environnement en développement web.
Un environnement de développement documenté dans un fichier texte dérive avec le temps ; un
environnement décrit dans une configuration exécutée ne peut pas diverger du projet qui le
définit.
Limites et pièges à connaître
Sur macOS et Windows, les performances d’E/S disque d’un volume Docker monté depuis l’hôte
restent sensiblement plus lentes que sur un système de fichiers natif Linux. Pour un projet
Node.js avec un node_modules volumineux, préférer un volume Docker nommé plutôt
qu’un montage direct du dossier hôte limite nettement l’impact sur les temps de build et de
rechargement à chaud.
Les images de base généreuses en outils préinstallés (plusieurs gigaoctets) ralentissent la
première ouverture du projet et la mise à jour de l’image en CI. Il vaut mieux partir d’une
image minimale et n’ajouter que les features réellement utilisées. Enfin, un
DevContainer standardise l’environnement de développement, pas l’environnement de production :
faire tourner les mêmes images en CI garantit la cohérence des tests, mais le déploiement reste
un sujet distinct, avec ses propres contraintes de taille et de sécurité d’image.
| Approche | Portée | Reproductibilité |
|---|---|---|
| README avec instructions manuelles | Documentation seule | Faible : dérive avec le temps |
| Docker Compose seul | Services et dépendances | Bonne pour les services, muette sur l’éditeur et ses extensions |
| Vagrant | Machine virtuelle complète | Bonne, mais plus lourde à provisionner qu’un conteneur |
| DevContainer | Runtime, outils, éditeur, ports | Élevée : un seul fichier versionné pilote l’éditeur |
Utiliser le même DevContainer en intégration continue
La spécification fournit aussi une interface en ligne de commande, devcontainer,
qui construit et exécute le même conteneur en dehors de tout éditeur. Un pipeline de CI peut
ainsi lancer les tests dans l’environnement exact que l’équipe utilise en local, sans
maintenir une seconde définition d’image en parallèle :
npm install -g @devcontainers/cli
devcontainer up --workspace-folder .
devcontainer exec --workspace-folder . -- npm test
Cette approche élimine une classe entière de régressions liées à la dérive entre
l’environnement de développement et celui de la CI : une dépendance système installée
manuellement par un développeur sur son poste, puis oubliée dans l’image utilisée par le
pipeline, cesse d’être un problème puisque les deux environnements partagent la même
définition. Le gain est net sur un projet PHP qui dépend d’extensions natives précises, ou sur
un projet Node.js qui embarque des modules compilés : la version de la bibliothèque système
sous-jacente ne varie plus selon la machine qui exécute le build.
La contrepartie est un temps de démarrage du pipeline légèrement supérieur à une image de CI
optimisée au minimum pour l’exécution des tests, puisque le conteneur embarque aussi les
extensions et outils destinés à l’éditeur. Pour les projets où ce coût pèse sur la durée totale
du pipeline, une image de CI dérivée de la même base, mais sans les couches spécifiques à
l’éditeur, reste une option raisonnable tant qu’elle est régénérée automatiquement à chaque
changement du Dockerfile source plutôt que maintenue à la main.
Ce qu’il faut retenir
La spécification Development Containers déplace la configuration de l’environnement de
développement d’une documentation à maintenir vers un fichier exécuté par l’éditeur. Elle est
désormais prise en charge nativement par VS Code, les IDE JetBrains et GitHub Codespaces, ce
qui en fait un standard réaliste plutôt qu’une solution propriétaire. Sa principale limite
reste la performance des E/S disque sur macOS et Windows, à anticiper par des volumes nommés
plutôt que par des montages directs du dossier hôte.
Sur les projets qu’il reprend chez des clients, l’auteur constate presque systématiquement
le même scénario : un README d’installation daté de plusieurs mois, devenu faux sur au moins
un point. Le DevContainer ne règle pas tout, mais il déplace la charge de la documentation vers
une configuration que l’éditeur vérifie à chaque ouverture du projet — ce qui, en pratique,
évite bien des aller-retours de debug le premier jour d’un nouveau contrat — Simon Janvier.
Pour aller plus loin : la spécification complète sur
containers.dev.
