Aller au contenu
Développement medium

Dev Containers : environnements de développement reproductibles

17 min de lecture

Dev Containers crée un environnement de développement identique pour tous les membres de votre équipe, quel que soit leur système d'exploitation. Fini le "ça marche sur ma machine" : les dépendances, versions et configurations sont définies dans un fichier versionné. Ce guide couvre la configuration de base, Docker Compose, les features, et l'intégration CI/CD.

  • Comprendre ce que résout un Dev Container.
  • Écrire un devcontainer.json (image, features, extensions).
  • Ouvrir un projet dans le conteneur avec VS Code.
  • Combiner avec Docker Compose pour les dépendances.
  • Réutiliser l'environnement en CI et GitHub Codespaces.

Sans environnement standardisé, chaque développeur a sa propre configuration :

ProblèmeConséquence
Versions différentes (Node, Python...)Bugs impossibles à reproduire
Dépendances système manquantes"Ça marche chez moi"
Configuration manuelleOnboarding de plusieurs heures
Pollution du système localConflits entre projets

Dev Containers résout ces problèmes avec Docker :

  • Environnement défini en code : fichier devcontainer.json versionné
  • Isolation complète : chaque projet a son container
  • Portabilité : fonctionne sur Linux, macOS, Windows
  • Onboarding instantané : clone le repo, ouvre VS Code, c'est prêt

Quand vous ouvrez un projet avec Dev Containers :

  1. VS Code lit .devcontainer/devcontainer.json
  2. Docker crée le container avec l'image spécifiée
  3. VS Code Server s'installe dans le container
  4. Extensions et dépendances s'installent automatiquement
  5. Votre code est monté dans le container (volume)

Vous travaillez dans VS Code normalement, mais tout s'exécute dans le container : terminal, debug, LSP, extensions.

  1. Visual Studio Code

    Téléchargez depuis code.visualstudio.com.

  2. Docker

    Le moteur de containers qui exécute l'environnement. Installez Docker Desktop ou Docker Engine.

    Fenêtre de terminal
    docker --version
    # Docker version 24.0.7 ou supérieur
  3. Extension Dev Containers

    Installez l'extension Dev Containers (anciennement "Remote - Containers") :

    Fenêtre de terminal
    code --install-extension ms-vscode-remote.remote-containers

    Ou recherchez "Dev Containers" dans le Marketplace (ID : ms-vscode-remote.remote-containers).

Un Dev Container tient dans un seul fichier versionné, devcontainer.json, placé dans un dossier .devcontainer/ à la racine du dépôt. VS Code le détecte automatiquement à l'ouverture du projet et propose de rouvrir la fenêtre dans le conteneur. Le minimum viable se réduit à trois informations : l'image de base, la commande d'installation des dépendances et les extensions à embarquer.

À la racine de votre projet, créez le dossier .devcontainer/ :

mon-projet/
├── .devcontainer/
│ └── devcontainer.json
├── src/
├── package.json
└── ...

Notez le tag d'image précis (node:20-alpine3.19) plutôt qu'un node:latest : sans lui, deux développeurs qui construisent le conteneur à deux mois d'écart n'obtiennent pas la même version de Node, et la reproductibilité promise s'évapore.

{
"name": "Astro Dev Container",
"image": "node:20-alpine3.19",
"postCreateCommand": "npm ci",
"customizations": {
"vscode": {
"extensions": [
"astro-build.astro-vscode",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode"
]
}
},
"forwardPorts": [4321]
}

Explication des champs :

ChampDescription
nameNom affiché dans VS Code
imageImage Docker à utiliser
postCreateCommandCommande exécutée après création (installation)
customizations.vscode.extensionsExtensions à installer automatiquement
forwardPortsPorts exposés sur localhost

La première ouverture est la plus longue : VS Code télécharge l'image, construit le conteneur, y installe le serveur VS Code puis les extensions. Les suivantes réutilisent le conteneur existant et démarrent en quelques secondes.

  1. Ouvrez le projet dans VS Code
  2. Appuyez sur F1Dev Containers: Reopen in Container
  3. VS Code télécharge l'image, crée le container, installe les extensions
  4. Votre terminal s'exécute maintenant dans le container

Vérification :

Fenêtre de terminal
# Dans le terminal VS Code (container)
node --version
# v20.x.x
cat /etc/os-release | grep PRETTY_NAME
# PRETTY_NAME="Alpine Linux v3.19"

Au-delà de l'image et des extensions, la spécification expose des propriétés qui décident quand les commandes s'exécutent, sous quel utilisateur et avec quoi monté. Ce sont elles qui font la différence entre un conteneur qui dépanne et un conteneur que l'équipe garde. Les quatre familles ci-dessous couvrent la quasi-totalité des besoins réels.

La spec définit plusieurs hooks exécutés à différents moments :

HookMomentUsage typique
postCreateCommandAprès création du containernpm ci, installation dépendances
postStartCommandÀ chaque démarrageLancer un serveur de dev
postAttachCommandÀ chaque connexion VS CodeMessages de bienvenue
initializeCommandAvant création (sur l'hôte)Clone de submodules

Exemple avec séparation des responsabilités :

{
"postCreateCommand": "npm ci",
"postStartCommand": "npm run dev -- --host"
}

containerEnv inscrit les variables dans le conteneur lui-même, donc dans le fichier versionné : réservez-le aux valeurs de développement inoffensives. Un vrai secret n'a rien à faire là, puisqu'il partirait dans le dépôt Git avec le reste de la configuration.

{
"containerEnv": {
"NODE_ENV": "development",
"DATABASE_URL": "postgres://user:pass@db:5432/mydb"
}
}

Par défaut, le container peut s'exécuter en root. Pour un comportement plus proche de la production :

{
"remoteUser": "node"
}

L'utilisateur node existe dans les images officielles Node.js. Pour d'autres images, utilisez containerUser ou créez l'utilisateur dans un Dockerfile.

Le montage ci-dessous est en lecture seule (readonly), ce qui empêche un processus du conteneur de modifier ou d'écraser vos clés privées. La variable ${localEnv:HOME} est résolue sur la machine hôte, pas dans le conteneur.

{
"mounts": [
"source=${localEnv:HOME}/.ssh,target=/home/node/.ssh,type=bind,readonly"
]
}

Cela monte vos clés SSH dans le container pour les opérations Git.

Les features sont des modules réutilisables qui installent des outils dans votre container. Plus propre que de tout faire dans un Dockerfile.

{
"image": "mcr.microsoft.com/devcontainers/base:ubuntu",
"features": {
"ghcr.io/devcontainers/features/node:1": {
"version": "20"
},
"ghcr.io/devcontainers/features/docker-in-docker:2": {},
"ghcr.io/devcontainers/features/git:1": {}
}
}

Features populaires :

FeatureDescription
nodeNode.js + npm
pythonPython + pip
docker-in-dockerDocker dans le container
kubectl-helm-minikubeOutils Kubernetes
terraformTerraform CLI
aws-cliAWS CLI

Consultez le catalogue de features pour la liste complète.

Quand votre projet nécessite plusieurs services (base de données, cache, API), utilisez Docker Compose.

Le service de développement doit rester vivant sans rien faire : c'est le rôle du command: sleep infinity. Sans lui, le conteneur web s'arrêterait immédiatement et VS Code n'aurait plus rien à quoi se rattacher. Le volume nommé postgres-data conserve la base entre deux reconstructions.

services:
web:
image: node:20-alpine3.19@sha256:1cc9088b0fbcb2009a8fc2cb57916cd129cd5e32b3c75fb12bb24bac76917a96
ports:
- "4321:4321"
volumes:
- ./:/workspace:cached
command: sleep infinity
db:
image: postgres:16@sha256:33f923b05f64ca54ac4401c01126a6b92afe839a0aa0a52bc5aeb5cc958e5f20
restart: unless-stopped
volumes:
- postgres-data:/var/lib/postgresql/data
environment:
POSTGRES_USER: myapp_user
POSTGRES_PASSWORD: myapp_password
POSTGRES_DB: myapp_db
ports:
- "5432:5432"
volumes:
postgres-data:

En mode Compose, image disparaît au profit de dockerComposeFile et de service : VS Code délègue la création des conteneurs à Compose et se rattache au seul service désigné. Le workspaceFolder doit correspondre au chemin monté dans ce service, ici /workspace.

{
"name": "Mon App + Postgres",
"dockerComposeFile": "../docker-compose.yml",
"service": "web",
"workspaceFolder": "/workspace",
"postCreateCommand": "npm ci",
"forwardPorts": [4321, 5432],
"customizations": {
"vscode": {
"extensions": [
"astro-build.astro-vscode",
"ms-azuretools.vscode-docker"
]
}
}
}

Champs spécifiques à Compose :

ChampDescription
dockerComposeFileChemin vers le docker-compose.yml
serviceService principal (où VS Code se connecte)
workspaceFolderDossier de travail dans le container

Depuis le service web, la base de données est accessible via le nom du service :

Fenêtre de terminal
# Dans le terminal du container
psql -h db -U myapp_user -d myapp_db

Depuis votre machine locale, utilisez localhost:5432 grâce à forwardPorts.

Pour des besoins spécifiques, créez un Dockerfile :

Ce Dockerfile crée un utilisateur non privilégié avec l'UID 1000, celui du premier compte sur la plupart des distributions Linux. La correspondance d'UID évite le problème classique des fichiers créés dans le conteneur qui apparaissent en root sur l'hôte.

FROM node:20-alpine3.19@sha256:1cc9088b0fbcb2009a8fc2cb57916cd129cd5e32b3c75fb12bb24bac76917a96
# Installer des outils supplémentaires
RUN apk add --no-cache \
git \
openssh-client \
curl
# Créer un utilisateur non-root
RUN adduser -D -u 1000 developer
USER developer
WORKDIR /workspace

Le champ build.context vaut ici .., soit la racine du dépôt et non le dossier .devcontainer/. C'est indispensable dès que le Dockerfile doit copier un fichier du projet, comme un package.json.

{
"name": "Custom Dev Container",
"build": {
"dockerfile": "Dockerfile",
"context": ".."
},
"remoteUser": "developer",
"postCreateCommand": "npm ci",
"forwardPorts": [4321]
}

Vous pouvez combiner l'accès SSH distant avec Dev Containers : VS Code se connecte en SSH à un serveur, puis utilise Docker sur ce serveur.

  1. Connectez-vous en SSH

    F1Remote-SSH: Connect to Host → sélectionnez votre serveur

  2. Ouvrez le projet

    Une fois connecté, ouvrez le dossier contenant .devcontainer/

  3. Ouvrez dans le container

    F1Dev Containers: Reopen in Container

Avantages :

  • Docker tourne sur le serveur (pas besoin de Docker local)
  • Ressources du serveur (RAM, CPU, stockage rapide)
  • Réseau du serveur (accès aux services internes)

Consultez la documentation officielle pour les configurations avancées.

Le devcontainer.json peut servir de source de vérité pour votre pipeline CI.

Le principe tient en une phrase : le Dev Container sert l'éditeur, le job CI tourne dans la même image de base. Tant que les deux partagent le tag exact, un test qui passe en local passe en CI pour les mêmes raisons, et un échec en CI se reproduit sur le poste du développeur.

Le job ci-dessous s'exécute directement dans l'image grâce à la clé container. Trois réflexes de durcissement l'accompagnent : permissions: {} au niveau du workflow retire tous les droits par défaut du GITHUB_TOKEN, chaque job redemande le strict minimum, et persist-credentials: false empêche actions/checkout de laisser le jeton dans le .git/config où n'importe quelle dépendance pourrait le lire.

.github/workflows/ci.yml
name: CI
on: [push, pull_request]
permissions: {}
jobs:
test:
runs-on: ubuntu-latest
permissions:
contents: read
container:
image: node:20-alpine3.19 # Même image que devcontainer.json
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: Build
run: npm run build

Pour une cohérence totale, utilisez la CLI devcontainer :

permissions: {}
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Build dev container
uses: devcontainers/ci@513af61f4de4f75d37e4438f184ba4358f0fc1ca # v0.3.1900000450
with:
runCmd: npm ci && npm test && npm run build

Cette approche utilise exactement la configuration de votre devcontainer.json. Les deux actions sont épinglées par SHA de commit, avec le tag en commentaire : un tag Git peut être redéplacé par son mainteneur vers un autre commit, un SHA non.

La lenteur ressentie d'un Dev Container vient presque toujours du système de fichiers, pas du CPU. Chaque lecture d'un fichier monté depuis l'hôte traverse une couche de traduction sur macOS et Windows, ce qui se paie très cher sur les milliers de petits fichiers d'un node_modules. Deux réglages suffisent à récupérer l'essentiel : placer le code au bon endroit et sortir les dépendances du montage.

Le tableau donne l'emplacement à privilégier selon l'hôte. Sur Windows, le point critique est de rester dans le système de fichiers WSL2 : un projet ouvert depuis /mnt/c/ traverse le pont vers NTFS à chaque accès et devient rapidement inutilisable.

OSRecommandation
LinuxCode sur le filesystem Linux, performances natives
macOSPréférer le code dans le container (volume nommé) pour éviter les I/O lents
WindowsStocker le code en WSL2 (\\wsl$\Ubuntu\...), pas sur /mnt/c/

Évitez de remonter node_modules depuis l'hôte :

{
"mounts": [
"source=myproject-node_modules,target=/workspace/node_modules,type=volume"
]
}

Cela stocke node_modules dans un volume Docker plus rapide.

La plupart des pannes se lisent dans le journal de création du conteneur, accessible par F1 puis Dev Containers: Show Container Log. Le tableau ci-dessous regroupe les symptômes récurrents ; deux d'entre eux, les fichiers appartenant à root et le port qui ne répond pas, proviennent d'un devcontainer.json mal structuré plutôt que d'un problème Docker.

SymptômeCause probableSolution
"Cannot connect to Docker"Docker non démarréLancez Docker Desktop
Container lent à créerTéléchargement de l'imageNormal la première fois, cache ensuite
Fichiers en root sur l'hôteremoteUser non configuréAjouter "remoteUser": "node"
Extensions non installéesMauvais format JSONVérifier customizations.vscode.extensions
Port non accessibleforwardPorts mal placéDoit être au premier niveau, pas dans customizations
Erreur "image not found"Typo dans le nom d'imageVérifier l'orthographe (alpine pas apline)

Si quelque chose ne fonctionne pas après une modification :

F1Dev Containers: Rebuild Container

Pour repartir de zéro (sans cache Docker) :

F1Dev Containers: Rebuild Container Without Cache

  1. Dev Containers = environnement en code, devcontainer.json définit tout
  2. Spec ouverte, Fonctionne avec VS Code, Codespaces, JetBrains, DevPod
  3. postCreateCommand pour l'installation, postStartCommand pour les process
  4. Features pour ajouter des outils sans Dockerfile
  5. Docker Compose pour les architectures multi-services
  6. forwardPorts au premier niveau, pas dans customizations
  7. Même image en CI, garantit la cohérence dev/production
  8. Remote SSH + DevContainers, Docker sur le serveur distant

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn