Motivation
Ein Baustein zur Risikominimierung (siehe The hidden dangers of AI coding) ist die Trennung der agentenunterstützten Entwicklung vom Rest des Systems.
Hierfür bieten sich Container an: Die KI-Tools werden ausschließlich in Containern ausgeführt, in denen sich neben dem Source Code des Projekts auch eine vollständige Entwicklungsumgebung befindet.
Dadurch wird zumindest die Ausführung der KI-Agenten auf Systemebene verhindert und die Entwickler laufen nicht Gefahr, dass der AI-Agent sudo rm -rf --no-preserve-root / ausführt, sobald er sich über einen pampigen Prompt des Entwicklers geärgert hat. Die Auswirkungen wären im Worst Case auf das lokale Dateisystem des Containers beschränkt.
In diesem Artikeln wollen wir skizzieren, wie man sich leicht mithilfe von Dev Containers so eine Umgebung einrichten kann. Beispielhaft setzen wir hierfür eine .NET-basierte Entwicklungsumgebung mit Claude Code auf.
Die Voraussetzungen hierfür sind:
Visual Studio Code
Docker
Dev Containers als Visual Studio Code Erweiterung
.NET
DevContainer einrichten
Erster Schritt ist das Anlegen eines JSON-Files zur Konfiguration des Containers. Das JSON-File kann entweder als .devcontainer.json im Root-Verzeichnis des Projekts liegen oder in einem Ordner .devcontainer als devcontainer.json ebenfalls im Root-Verzeichnis.
Letzteres bietet sich an, da man dort auch ein Dockerfile, ein docker-compose.yml und weitere Skripte ablegen kann und diese dann im Konfigurationsfile referenziert werden können.
Im JSON-File wird das zu verwendende Docker-Image (oder alternativ ein Dockerfile) angegeben. Microsoft liefert hierfür einige vorgefertigte Images, bspw. für die Entwicklung in C++, Java, Node, Go oder auch .NET.
Für unsere Zwecke verwenden wir .NET 9. Das gewünschte Image wird in devcontainer.json wie folgt spezifiziert:
"image": "mcr.microsoft.com/devcontainers/dotnet:9.0-noble"
Kurz zur Technik: Der Development Container hostet einen Visual Studio Code Server, mit dem sich das lokal installierte VS Code verbindet. Die Dateien aus dem Workspace werden standardmäßig in den Container gemountet.
Hat sich das lokale VS Code mit einem DevContainer verbunden, so stehen nur noch Theme/UI-Erweiterungen zur Verfügung. Entsprechend muss man die Erweiterungen, die für die Entwicklung benötigt werden, auch im DevContainer zur Verfügung stellen. Im Falle von .NET kann das beispielsweise das C# Dev Kit sein. Zusammen mit anderen Erweiterungen definiert man dieses in der devcontainer.json beispielhaft wie folgt:
"customizations": {
"vscode": {
"extensions": [
"ms-dotnettools.csdevkit",
"tintoy.msbuild-project-tools",
"timheuer.resx-editor",
"gruntfuggly.todo-tree"
]
}
}
Dadurch sind die aufgelisteten Erweiterungen automatisch nach dem Start des DevContainers verfügbar.
Das reicht im Grunde schon aus, um mit dem Container arbeiten zu können. Der Container muss nur noch über den Befehl Dev Containers: Rebuild Container in Visual Studio Code gebaut werden. Anschließend verbindet man VS Code per Dev Containers: Reopen in Container mit dem gebauten Container.
Das Bauen des Containers ist nur bei einer Konfigurationsänderung erforderlich.
Installation von Claude Code
Claude Code benötigt Node.js >= 18. Das lässt sich am einfachsten über ein sogenanntes Feature installieren.
"features": {
"ghcr.io/devcontainers/features/node": {
"version": "22"
}
}
Features sind vorgefertigte Pakete, um ohne großen Konfigurationsaufwand eben solche Tools wie Node.js im Development Container verfügbar zu machen.
Eine Liste der verfügbarne Features findet man hier.
Claude Code kann installiert werden, nachdem der Container fertig gebaut wurde. Hierfür bietet sich die postCreateCommand-Property an, die - wenig überraschend - den spezifizierten Befehl nach dem Bau des Containers ausführt:
"postCreateCommand": "npm install -g @anthropic-ai/claude-code"
Jetzt steht Claude Code im DevContainer zur Verfügung und kann über den Befehl claude aufgerufen werden.
Beim ersten erfolgreichen Start installiert claude außerdem automatisch die Claude Code Erweiterung in Visual Studio Code. Dadurch hat Claude auf die aktuell in der Entwicklungsumgebung geöffneten Dateien sowie die markierte Zeilen Zugriff und diese stehen als Kontext zur Verfügung. Außerdem sind die von Claude vorgeschlagenen Codeänderungen in der IDE als Diff sichtbar.
Git konfigurieren
DevContainers kopiert automatisch das lokale .gitconfig-File in den Container, sodass man solche Dinge wie user.name usw. in der Regel nicht konfigurieren muss.
Etwas unschön (um es vorsichtig auszudrücken) ist allerdings die Tatsache, dass die DevContainers-Erweiterung standardmäßig laufende SSH-Agents in den Container forwarded. Standardmäßig hat Claude also prinzipiell Zugriff auf die SSH-Keys und kann damit allen möglichen Schaden in den GitHub-/GitLab-Repositorys des Entwicklers anrichten.
Wer auf Nummer sicher gehen will, lässt als weiteres postCreateCommand folgenden Befehl laufen:
# Force remove VS Code injected SSH socket files
# (see https://stackoverflow.com/questions/79304417/vscode-how-to-disable-ssh-exposure-into-devcontainer)
find /tmp -maxdepth 1 -name 'vscode-ssh-auth-*.sock' -delete || true
Dadurch hat der Container keinen Zugriff mehr auf den SSH-Agent und folglich auch nicht mehr auf die Keys. Der sinnvollere Weg, dennoch auf die Repositorys zugreifen zu können, läuft über Access Tokens. Diese lassen sich feingranular einstellen, sodass der von Claude unter Umständen angerichtete Schaden zumindest auf ein Repository beschränkt ist.
Damit auch weiterhin die Installation von Claude als postCreateCommand ausgeführt wird, führt man am besten ein Shellskript post-create-commands.sh ein:
"postCreateCommand": "bash .devcontainer/post-create-commands.sh"
#!/usr/bin/env bash
# post-create-commands.sh
# Force remove VS Code injected SSH socket files
# (see https://stackoverflow.com/questions/79304417/vscode-how-to-disable-ssh-exposure-into-devcontainer)
find /tmp -maxdepth 1 -name 'vscode-ssh-auth-*.sock' -delete
# Install Claude
npm install -g @anthropic-ai/claude-code
ASP.NET-spezifisch: Dev-Zertifikate installieren
Für die ASP.NET-Entwicklung mit HTTPS ist es notwendig, Development-Zertifikate im Container zu installieren und diesen zu vertrauen. Dies geschieht am besten über das onCreateCommand, da die Zertifikate nur einmal pro Container-Lebenszyklus erstellt werden müssen.
Hierfür nutzen wir wieder ein Shellskript setup-dotnet-dev-cert.sh, das von onCreateCommand aufgerufen wird:
#!/usr/bin/env bash
# Change ownership of the .dotnet directory to the vscode user (to avoid permission errors)
sudo chown -R vscode:vscode /home/vscode/.dotnet
# If there is no development certificate, this command will generate a new one
dotnet dev-certs https
# Export the ASP.NET Core HTTPS development certificate to a PEM file
sudo -E dotnet dev-certs https --export-path /usr/local/share/ca-certificates/dotnet-dev-cert.crt --format pem
# Add the PEM file to the trust store
sudo update-ca-certificates
Es übernimmt folgende Aufgaben:
Berechtigungen setzen: Die .dotnet-Verzeichnisse werden dem vscode-Benutzer des Containers zugewiesen
Zertifikat generieren: Falls noch kein Development-Zertifikat existiert, wird ein neues erstellt
Zertifikat exportieren: Das Zertifikat wird als PEM-Datei exportiert
Vertrauensstellung: Das Zertifikat wird zum System-Truststore hinzugefügt
Der mounts-Eintrag in der Konfiguration sorgt dafür, dass die Zertifikate persistent über Container-Rebuilds hinweg verfügbar bleiben:
"mounts": [
{
"type": "volume",
"source": "x509stores",
"target": "/home/vscode/.dotnet/corefx/cryptography/x509stores"
}
]
Dadurch können HTTPS-Anwendungen im DevContainer ohne Warnung vor einem nicht vertrauten Zertifikat entwickelt und getestet werden.
Gesamte Konfiguration
Hier noch einmal der Überblick über die finale Konfiguration devcontainer.json:
{
"image": "mcr.microsoft.com/devcontainers/dotnet:9.0-noble",
"customizations": {
"vscode": {
"extensions": [
"ms-dotnettools.csdevkit",
"tintoy.msbuild-project-tools",
"timheuer.resx-editor",
"gruntfuggly.todo-tree"
]
}
},
"features": {
"ghcr.io/devcontainers/features/node": {
"version": "22"
}
},
"mounts": [
{
"type": "volume",
"source": "x509stores",
"target": "/home/vscode/.dotnet/corefx/cryptography/x509stores"
}
],
"onCreateCommand": "bash .devcontainer/setup-dotnet-dev-cert.sh",
"postCreateCommand": "bash .devcontainer/post-create-commands.sh"
}
Vor- und Nachteile
Vorteile:
Isolation von Claude: Claude hat nur Zugriff auf die in den Container gemounteten Dateien.
Isolierte Umgebung: Keine Konflikte mit lokalen Installationen.
Claude-Integration: Claude nutzt die in der IDE geöffneten Dateien und markierten Zeilen als Kontext.
Sichtbare Änderungen: Codeänderungen von Claude werden als Diff in der IDE angezeigt.
Konsistente Entwicklungsumgebung: Alle Entwickler arbeiten mit identischen, reproduzierbaren Abhängigkeiten und Konfigurationen.
Plattformunabhängigkeit: Funktioniert auf Windows, macOS und Linux identisch.
Nachteile:
Erneuter Login nach Rebuild: Änderungen an der devcontainer.json führen unweigerlich zu einem Rebuild des Containers. Dadurch geht der Claude-Login verloren und man muss sich neu authentifizieren.
Resource-Verbrauch: Docker-Container benötigen zusätzlichen Speicher und CPU.
Langsamere Dateizugriffe: Gemountete Dateien können Performance-Einbußen verursachen.
Komplexere Debugging-Szenarien: Debugging zwischen Host und Container kann herausfordernd sein.
Längere Startzeiten: Container-Build und -Start dauern länger als lokale Entwicklung.
Nützliche Referenzen
devcontainer.json-Referenz: https://containers.dev/implementors/json_reference
Vorgefertigte Images: https://github.com/devcontainers/images/tree/main/src
Vorgefertigte Features: https://github.com/devcontainers/features/tree/main/src
Dev Container Spezifikation: https://containers.dev
ASP.NET Dev-Zertifikate für DevContainers: https://github.com/devcontainers/images/tree/main/src/dotnet#enabling-https-in-aspnet-core-by-creating-a-dev-certificate