„Bei mir funktioniert’s“ gehört zu den teuersten Sätzen in einem Web-Entwicklungsteam:
unterschiedliche Node- oder PHP-Versionen von Rechner zu Rechner, eine fehlende
Editor-Erweiterung, eine vergessene Umgebungsvariable. Die Development-Containers-Spezifikation,
maßgeblich von Microsoft vorangetrieben und inzwischen von mehreren Tool-Anbietern umgesetzt,
begegnet diesem Problem, indem sie die komplette Entwicklungsumgebung — Laufzeitumgebung,
Werkzeuge, Erweiterungen, Ports — in einer mit dem Code versionierten Datei beschreibt.
Das Problem, das DevContainers lösen
Ein Webprojekt hängt meist von einer genauen Liste an Voraussetzungen ab: einer
Laufzeitversion (Node, PHP, Python), Kommandozeilen-Werkzeugen, manchmal einer lokalen
Datenbank, gemeinsamen Editor-Erweiterungen für Linting oder Formatierung. Diese Liste in einer
README zu dokumentieren funktioniert eine Weile, bis das Dokument von der
Realität abweicht. Die
für jedes Projekt spezifischen Kommandozeilen-Werkzeuge
unterscheiden sich am Ende von Rechner zu Rechner, ohne dass es jemandem auffällt — bis ein
schwer reproduzierbarer Bug auftaucht.
Ein DevContainer verlagert diese Dokumentation von einem zu lesenden Text hin zu einer
Konfiguration, die der Editor tatsächlich ausführt: Er baut einen Container, installiert darin
die vorgesehenen Werkzeuge, öffnet dort den Projektordner und gibt die benötigten Ports frei.
Ein neues Teammitglied installiert auf dem Host-Rechner nichts mehr außer Docker und seinem
Editor.
Anatomie einer devcontainer.json
Die Konfiguration liegt in .devcontainer/devcontainer.json im Wurzelverzeichnis
des Repositorys. Ein Beispiel für ein Node.js-Projekt mit PostgreSQL:
{
"name": "node-web-app",
"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"
}
„Features“ sind wiederverwendbare Bausteine, die eine Laufzeitumgebung oder ein Werkzeug
(Node, PHP, Docker-in-Docker, GitHub CLI) installieren, ohne ein Dockerfile von
Hand zu schreiben. Der Block customizations.vscode installiert automatisch die
Erweiterungen und Einstellungen des Teams, sodass Linting und Formatierung für alle identisch
sind, sobald das Projekt geöffnet wird.
Welche Werkzeuge die Spezifikation unterstützen
| Werkzeug | Unterstützung | Hinweis |
|---|---|---|
| Visual Studio Code | Nativ (Dev-Containers-Erweiterung) | Ursprüngliche Referenzimplementierung der Spezifikation |
| GitHub Codespaces | Nativ | Liest devcontainer.json direkt, um die Cloud-Instanz zu provisionieren |
| JetBrains (IntelliJ, PhpStorm, WebStorm) | Nativ seit 2024 | Gleiche Dateien, keine Konvertierung nötig |
devcontainer-CLI | Nativ | Baut und startet den Container außerhalb des Editors, nützlich in der CI |
| Docker Compose allein | Kompatibel als Ergänzung | Ein DevContainer kann auf einer bestehenden docker-compose.yml aufbauen |
Einen DevContainer in einem bestehenden Projekt einrichten
In einem Projekt, das bereits mit Docker Compose läuft, läuft die Migration auf drei
Schritte hinaus: einen .devcontainer-Ordner hinzufügen, ihn auf die bestehende
Compose-Datei verweisen lassen statt eine neue zu schreiben, und dann die Ports sowie den
Post-Create-Befehl deklarieren. Für ein Team, das mit
mehreren parallelen Git-Worktrees
jongliert, kann jeder Worktree seinen eigenen, isolierten DevContainer mit eigenen Ports und
eigenem Datenbankstand öffnen, ohne die anderen Arbeitskopien zu stören.
Secrets dürfen niemals in devcontainer.json geschrieben werden, die mit dem
restlichen Repository versioniert wird. Sie werden dem Container über die
Umgebungsvariablen des Hosts übergeben (ein remoteEnv– oder
containerEnv-Feld, das auf eine nicht versionierte Variable verweist) — nach
derselben Logik wie beim
sauberen Umgang mit Umgebungsvariablen in der Webentwicklung.
Eine in einer Textdatei dokumentierte Entwicklungsumgebung driftet mit der Zeit ab; eine in
einer ausgeführten Konfiguration beschriebene kann nicht vom Projekt abweichen, das sie
definiert.
Grenzen und Fallstricke
Unter macOS und Windows bleibt die Festplatten-E/A-Leistung eines vom Host gemounteten
Docker-Volumes spürbar langsamer als auf einem nativen Linux-Dateisystem. Bei einem
Node.js-Projekt mit einem umfangreichen node_modules-Ordner reduziert ein
benanntes Docker-Volume gegenüber einem direkten Mount des Host-Ordners die Auswirkungen auf
Build-Zeiten und Hot-Reload deutlich.
Basis-Images, die mit vorinstallierten Werkzeugen überladen sind (mehrere Gigabyte),
verlangsamen das erste Öffnen des Projekts und die Image-Aktualisierung in der CI. Besser ist
es, von einem minimalen Image auszugehen und nur die tatsächlich genutzten Features
hinzuzufügen. Schließlich standardisiert ein DevContainer die Entwicklungsumgebung, nicht die
Produktionsumgebung: Dieselben Images in der CI laufen zu lassen, sorgt für konsistente Tests,
aber das Deployment bleibt ein eigenes Thema mit eigenen Anforderungen an Image-Größe und
-Sicherheit.
| Ansatz | Umfang | Reproduzierbarkeit |
|---|---|---|
| README mit manuellen Anweisungen | Nur Dokumentation | Gering: driftet mit der Zeit ab |
| Docker Compose allein | Dienste und Abhängigkeiten | Gut für Dienste, sagt nichts über Editor und Erweiterungen |
| Vagrant | Vollständige virtuelle Maschine | Gut, aber aufwendiger zu provisionieren als ein Container |
| DevContainer | Laufzeitumgebung, Werkzeuge, Editor, Ports | Hoch: eine einzige versionierte Datei steuert den Editor |
Denselben DevContainer in der Continuous Integration nutzen
Die Spezifikation bringt auch eine Kommandozeilen-Schnittstelle namens devcontainer
mit, die denselben Container außerhalb jedes Editors baut und ausführt. Eine CI-Pipeline kann
so Tests in genau der Umgebung ausführen, die das Team lokal nutzt, ohne eine zweite
Image-Definition parallel zu pflegen:
npm install -g @devcontainers/cli
devcontainer up --workspace-folder .
devcontainer exec --workspace-folder . -- npm test
Dieser Ansatz beseitigt eine ganze Klasse von Regressionen, die durch Drift zwischen
Entwicklungs- und CI-Umgebung entstehen: Eine von Hand auf dem Rechner eines Entwicklers
installierte Systemabhängigkeit, die dann im Image der Pipeline vergessen wurde, ist kein
Problem mehr, sobald beide Umgebungen dieselbe Definition teilen. Der Gewinn ist bei einem
PHP-Projekt, das von bestimmten nativen Erweiterungen abhängt, oder einem Node.js-Projekt mit
kompilierten Modulen deutlich spürbar: Die Version der zugrunde liegenden Systembibliothek
hängt nicht mehr von der Maschine ab, die den Build ausführt.
Der Nachteil ist eine etwas längere Startzeit der Pipeline im Vergleich zu einem auf das
Nötigste für die Testausführung reduzierten CI-Image, da der Container auch die für den Editor
gedachten Werkzeuge und Erweiterungen mitführt. Für Projekte, bei denen dieser Mehraufwand ins
Gewicht fällt, bleibt ein von derselben Basis abgeleitetes CI-Image ohne die
editor-spezifischen Schichten eine sinnvolle Option — solange es bei jeder Änderung am
Quell-Dockerfile automatisch neu erzeugt wird, statt von Hand gepflegt zu werden.
Was man sich merken sollte
Die Development-Containers-Spezifikation verlagert die Konfiguration der
Entwicklungsumgebung von einer zu pflegenden Dokumentation hin zu einer Datei, die der Editor
tatsächlich ausführt. Sie wird inzwischen nativ von VS Code, JetBrains-IDEs und GitHub
Codespaces unterstützt, was sie zu einem realistischen Standard statt einer proprietären
Lösung macht. Ihre Hauptgrenze bleibt die Festplatten-E/A-Leistung unter macOS und Windows, am
besten über benannte Volumes statt direkter Host-Ordner-Mounts zu umgehen.
Bei Projekten, die er für Kunden übernimmt, beobachtet der Autor fast immer dasselbe
Szenario: eine mehrere Monate alte Installations-README, die inzwischen an mindestens einer
Stelle nicht mehr stimmt. Ein DevContainer löst nicht alles, verlagert aber die Last der
Dokumentation auf eine Konfiguration, die der Editor bei jedem Öffnen des Projekts prüft — was
in der Praxis am ersten Tag eines neuen Auftrags einiges an Debugging-Hin-und-Her erspart —
Simon Janvier.
Zum Weiterlesen: die vollständige Spezifikation auf
containers.dev.
