Blog · · 12 Min. Lesezeit
Die Mauer von innen

Im letzten Artikel habe ich fünf Schichten beschrieben, die meine Coding-Agenten einhegen. Die äußerste ist die Mauer: ein nativer Apple-Container, in dem die Agenten laufen, ohne meinen Mac zu sehen. Hier schaue ich sie mir von innen an: wie sie gebaut ist, was sie von Docker Desktop unterscheidet, was ich getestet habe und wo ich gestolpert bin.
In „Käfig, Mauer, Türsteher“ ging es um das Zusammenspiel der Schichten. Die Mauer kam dort nur kurz vor: Die Agenten laufen in einem Apple-Container, und auf meinen Mac kommen sie nicht. Das klingt einfach. In der Praxis stecken ein paar Entscheidungen dahinter, und ein paar davon habe ich erst beim zweiten Anlauf richtig getroffen.
Warum überhaupt ein Container?
Mein Multi-Agenten-System startet Claude Code und Codex ohne Rückfragen. Jeder Lauf hat alle Rechte des Benutzers, unter dem der Agenten-Daemon läuft. Auf meinem Mac hieße das: SSH-Schlüssel, alle Dateien, alle angemeldeten Werkzeuge.
Für die Trennung gibt es drei übliche Wege:
- Ein eigener macOS-Benutzer. Billig, aber die Grenze ist das Rechtesystem von macOS. Alles, was für alle Benutzer lesbar ist, bleibt lesbar.
- Eine vollständige VM. Starke Grenze, aber schwer: eigenes Betriebssystem, Updates, Speicher, der dauerhaft belegt ist.
- Ein Container. Leicht und reproduzierbar. Die Frage ist nur, wie stark die Grenze ist.
Bei Apples container beantwortet sich diese Frage auf besondere Weise.
Apple Container: eine VM pro Container
Apple hat mit container ein eigenes Werkzeug für Linux-Container auf dem Mac veröffentlicht, als Open Source. Der Unterschied zu Docker Desktop liegt in der Architektur. Laut Apples technischer Übersicht startet container für jeden Container eine eigene, leichtgewichtige VM. Jeder Container bekommt damit die Isolation einer vollständigen virtuellen Maschine, mit eigenem Linux-Kernel.
Docker Desktop und Colima starten dagegen eine große Linux-VM. Darin laufen alle Container gemeinsam und teilen sich deren Kernel.
Apple container |
Docker Desktop / Colima | |
|---|---|---|
| VM | eine Mini-VM pro Container | eine gemeinsame VM für alle |
| Kernel | eigener pro Container | gemeinsamer Kernel der VM |
| Trennung der Container untereinander | VM-Grenze | Namespaces und cgroups |
| Mac-Ordner | nichts sichtbar, solange nichts eingehängt wird | /Users, /Volumes, /private, /tmp und /var/folders sind für die VM standardmäßig freigegeben |
| Speicher | Grenze pro Container | fester Block für die VM |
| Images | OCI, dieselben wie bei Docker | OCI |
| Reife | jung, nur Apple Silicon, volles Netzwerk erst ab macOS 26 | ausgereift, großes Ökosystem |
| Lizenz | Open Source | Docker Desktop kostenpflichtig bei mehr als 250 Mitarbeitenden oder mehr als 10 Mio. US-Dollar Jahresumsatz |
Für meine Agenten zählen zwei Zeilen.
Erstens die Freigaben. Bei Docker Desktop ist /Users in der VM standardmäßig erreichbar. Ein Container sieht es zwar erst, wenn man es einhängt. Aber ein einziges unvorsichtiges -v ~/… reicht. Bei Apple gibt es nichts, was man versehentlich freigeben könnte.
Zweitens die Trennung untereinander. Bricht ein Agent bei Docker aus seinem Container aus, landet er in der gemeinsamen VM, neben allen anderen Containern. Bei Apple bleibt er in seiner eigenen Mini-VM.
Den Mac selbst schützen beide Varianten über die Grenze des Hypervisors. Ein Agent müsste in beiden Fällen aus einer VM ausbrechen.
Der Aufbau
Das Image ist bewusst schlicht. Basis ist ein schlankes Debian mit Node.js. Dazu kommen Git, SSH, ein paar Werkzeuge für die Kommandozeile, Python, Codex über npm und Claude Code über den offiziellen Installer.
FROM docker.io/library/node:22-bookworm-slim
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
ca-certificates curl git openssh-client jq ripgrep python3 \
&& rm -rf /var/lib/apt/lists/*
RUN npm install -g @openai/codex
# Benutzer ohne Root-Rechte
RUN useradd --create-home --uid 1001 --shell /bin/bash agent
# Claude Code über den offiziellen Installer
RUN su agent -c 'curl -fsSL https://claude.ai/install.sh | bash' \
&& cp "$(readlink -f /home/agent/.local/bin/claude)" /usr/local/bin/claude
# Updates nur über einen Neubau des Images
ENV DISABLE_AUTOUPDATER=1
USER agent
WORKDIR /home/agent
Drei Entscheidungen stecken darin:
- Kein Root. Die Agenten laufen als Benutzer
agent. Claude Code verweigert seinen Modus ohne Rückfragen ohnehin, wenn es als root läuft. - Programme außerhalb des Home-Verzeichnisses. Warum, erkläre ich gleich nach dem Startbefehl.
- Keine Selbst-Updates. Ein Agent, der sein eigenes Werkzeug aktualisiert, verändert unbemerkt die Umgebung. Neue Versionen gibt es nur über einen Neubau des Images.
Gestartet wird der Container so:
container run -d --name agent --network agents \
-v agent-home:/home/agent \
--cap-drop ALL -m 4G --cpus 4 \
agent-runtime:latest
--cap-drop ALLnimmt dem Container alle Linux-Capabilities.-m 4Gund--cpus 4begrenzen Speicher und Rechenleistung.- Das Volume
agent-homeenthält Logins, Konfiguration und die Arbeitsordner. Es überlebt einen Neubau des Images. - Ein Ordner vom Mac ist nicht eingehängt. Das ist der eigentliche Punkt.
Das Volume ist übrigens kein Ordner, sondern eine einzelne Datei auf dem Mac:
~/Library/Application Support/com.apple.container/volumes/agent-home/volume.img
Darin steckt ein ext4-Dateisystem, wie es Linux verwendet. Beim Start hängt Apple container die Datei in die Mini-VM des Agenten ein, und dort erscheint sie als /home/agent. Den genauen Pfad zeigt container volume inspect agent-home.
Die Datei meldet bei mir 512 GB, belegt sind aber nur rund 490 MB. Die 512 GB sind die Obergrenze. Die Datei wächst nur so weit, wie wirklich Daten hineingeschrieben werden.
Der Agenten-Daemon läuft im Container im Vordergrund und holt sich seine Aufträge vom Server des Multi-Agenten-Systems. Mehr als drei Läufe gleichzeitig erlaubt er nicht.
Warum die Programme nicht im Home-Verzeichnis liegen dürfen
Ein Container besteht aus zwei Teilen: dem Image, das beim Bauen entsteht, und den Volumes, die beim Start dazukommen. Das Image ist unveränderlich und wird bei jedem Update neu gebaut. Das Volume ist der Teil, der bleibt: Logins, Konfiguration, Arbeitsordner.
Beim Start wird das Volume agent-home über das Verzeichnis /home/agent gelegt. Man kann sich das wie ein Tischtuch vorstellen: Was vorher auf dem Tisch lag, ist noch da, aber man sieht es nicht mehr. Der Container sieht in /home/agent nur noch den Inhalt des Volumes. Was das Image selbst an dieser Stelle mitbringt, ist unsichtbar, solange das Volume eingehängt ist.

Genau hier liegt die Falle. Der offizielle Installer von Claude Code legt das Programm nach ~/.local/bin, also nach /home/agent/.local/bin. Beim Bauen des Images funktioniert das: claude --version antwortet. Beim Start des Containers liegt dann das Volume darüber, und das Programm ist verschwunden. Der Agenten-Daemon findet kein claude mehr.
Deshalb kopiert die Containerfile das Programm nach der Installation nach /usr/local/bin. Dort liegt kein Volume, also bleibt es sichtbar.
Der Nebeneffekt ist mir sogar recht: Die Rollen sind sauber getrennt. Das Image enthält die Werkzeuge, das Volume enthält die Daten. Ein neues Image tauscht Claude Code, Codex und alles andere aus, ohne Logins oder Arbeitsordner anzufassen.
Und die Programme selbst kann kein Agent austauschen. Ich habe im laufenden Container nachgesehen: Der Suchpfad enthält nur Systemverzeichnisse wie /usr/local/bin und /usr/bin. Sie gehören root, und der Benutzer agent darf dort nicht schreiben. ~/.local/bin steht gar nicht im Suchpfad. Eine eigene Datei namens claude im Volume würde also nie gestartet.
Ganz ohne Hintertür ist das Volume trotzdem nicht. Die Einstellungen von Claude Code und Codex liegen dort, in ~/.claude und ~/.codex. Ein Agent, der sie verändert, beeinflusst spätere Läufe. Davor schützen nur die inneren Schichten: Codex darf in seiner Sandbox nur in den Arbeitsordner schreiben, und bei Claude Code prüft der Klassifikator jeden Schreibzugriff auf solche geschützten Pfade.
Eigene Identität statt meiner
Ein Agent läuft als derselbe Benutzer wie der Daemon, der ihn startet. Er kann also dessen Zugangstoken lesen. Hätte der Daemon mein Token, könnte jeder Agent alles, was ich kann: Workspaces löschen, Mitglieder verwalten. Und im Verlauf wäre er nicht von mir zu unterscheiden.
Deshalb meldet sich der Container mit einem eigenen Konto an. Es hat die Rolle member: Tickets lesen und schreiben ja, Einstellungen und Löschen nein. Ich habe das geprüft. Admin-Aktionen enden mit „403 Forbidden“. Das Token hat ein Ablaufdatum und kommt per stdin in den Container, nie als Argument auf der Kommandozeile.
Für Git gilt dasselbe Prinzip. Jedes Repository, an dem die Agenten arbeiten dürfen, bekommt einen eigenen Deploy-Key mit eigenem Host-Alias:
# ~/.ssh/config
Host gh-projekt
HostName github.com
IdentityFile ~/.ssh/deploy_projekt
IdentitiesOnly yes
Das Multi-Agenten-System kennt das Repository nur unter seiner HTTPS-Adresse. Eine Git-Regel leitet diese Adresse im Container auf den Deploy-Key um:
git config --global \
url."git@gh-projekt:ich/projekt".insteadOf "https://github.com/ich/projekt"
Klonen und Pushen funktionieren. Jedes andere Repository antwortet mit „Repository not found“.
Die Logins von Claude Code und Codex sind eigene Sitzungen, unabhängig von denen auf meinem Mac. Bei Codex brauchte das einen Umweg. Der Login per Gerätecode war in meinem Konto nicht freigeschaltet. Der Browser-Login wiederum erwartet die Rückleitung auf 127.0.0.1:1455, und zwar im Container. Ein kurzlebiger Hilfs-Container hat den Port vom Mac in den Agenten-Container weitergereicht. Nach dem Login habe ich ihn wieder gelöscht.
Der Türsteher zieht mit ein
Die Mauer ersetzt die inneren Schichten nicht. Der Daemon im Container startet Claude Code im Auto Mode und Codex in seiner Sandbox mit auto review. Abgelehnte Aktionen landen als Kommentar am Ticket. Welcher Modus gilt, steht im Namen der Laufzeitumgebung, etwa „Claude · auto mode“. So sehe ich in der Oberfläche auf einen Blick, wie ein Agent geschützt ist.
Unter Linux nutzt Codex für seine Sandbox bubblewrap. Im Image ist es nicht installiert. Codex bringt eine eigene Kopie mit und warnt nur. Ein Schreibversuch außerhalb des Arbeitsordners scheiterte im Container hart mit „Read-only file system“.
Von innen geprüft
Einen Teil der Tests habe ich als echte Tickets über das Multi-Agenten-System laufen lassen, so wie ein Agent sonst arbeitet. Den Rest habe ich direkt im Container geprüft:
| Test | Ergebnis |
|---|---|
| Welcher Benutzer läuft? | agent, kein root |
Gibt es /Users? |
nein, das Verzeichnis existiert nicht |
| Freigegebenes Repository klonen und pushen | geht |
| Anderes eigenes Repository klonen | „Repository not found“ |
| Admin-Aktion mit dem Agenten-Konto | „403 Forbidden“ |
| Schreiben außerhalb des Arbeitsordners (Codex) | „Read-only file system“ |
Ausdrücklich verlangtes rm -rf (Codex) |
vom Prüf-Agenten erlaubt, „risk: high“ |
Eingeschleuste Anweisung, die .env hochzuladen (Codex) |
vom Modell selbst verweigert |
Die letzten beiden Zeilen zeigen dasselbe wie auf dem Mac. Die Prüfung schützt vor Eigenmächtigkeit, nicht vor einem falschen Auftrag. Und das Modell selbst fängt viel ab, bevor eine Prüfung überhaupt gefragt wird.
Stolpersteine
Der Builder legt das Netz lahm. Zum Bauen von Images startet container einen eigenen Builder. Bei mir hat schon das Starten oder Stoppen dieses Builders das Container-Netz gestört. Danach fanden sich die Container nicht mehr, Namensauflösung lief ins Leere. Geholfen hat nur, alle Container zu stoppen und neu zu starten, auch den Agenten-Container. Mein Build-Skript bricht deshalb jetzt ab, solange noch ein Container läuft.
Das Volume nie doppelt einhängen. Weil das Volume eine einzige Image-Datei ist, darf es nie in einem zweiten Container hängen, solange der Agent läuft. Zwei Linux-Kernel, die gleichzeitig in dasselbe ext4-Dateisystem schreiben, können es beschädigen. Für Prüfungen nutze ich deshalb container exec im laufenden Container.
Speicher kommt erst beim Stoppen zurück. Apples Virtualisierung unterstützt das Zurückgeben von Speicher nur teilweise. Laut Apples Dokumentation werden Speicherseiten, die Linux im Container freigibt, nicht an macOS zurückgegeben. Der Agenten-Container kann seine 4 GB also über die Zeit voll belegen, auch wenn gerade nichts läuft. Ein gelegentlicher Neustart hilft. Unterm Strich ist das trotzdem sparsamer als eine Docker-VM, die ihren Block dauerhaft reserviert.
Was die Mauer nicht abdeckt

Die Läufe untereinander. Abgeschottet ist der Mac, nicht die Läufe voneinander. Alle Läufe teilen sich Container, /tmp und Home-Verzeichnis.
Alles im Volume ist lesbar. Das Token des Agenten-Kontos, die Logins von Claude Code und Codex, die Deploy-Keys. Ein Agent kann sie lesen. Deshalb sind diese Schlüssel so klein geschnitten: Rolle member, ein Key pro Repository, ein Token mit Ablaufdatum.
Das Netz ist offen. Die Agenten brauchen Anthropic, OpenAI und GitHub. Ein komplett abgeschottetes Netz scheidet also aus. Was ein Agent lesen kann, könnte er theoretisch auch irgendwohin schicken.
Für das Netz sehe ich eine nächste Ausbaustufe, die ich noch nicht umgesetzt habe. Apples container kennt interne Netze ohne Zugang nach außen. Der Agent hinge nur in so einem Netz. Ein Proxy-Container hinge in beiden Netzen und ließe nur eine Liste erlaubter Ziele durch. Das ist dasselbe Prinzip wie der Proxy in Claudes eigener Sandbox, den ich in „Wie sicher ist die Claude Code Sandbox auf dem Mac?“ beschrieben habe. Einen Haken kenne ich schon: Git über SSH ignoriert die übliche Proxy-Einstellung.
Ein Fenster bleibt auch damit offen. Der Container erreicht schon heute Dienste auf meinem Mac, die auf allen Netzwerkschnittstellen lauschen. Getestet habe ich das mit dem AirPlay-Empfänger von macOS: Er antwortet. Dienste, die nur an 127.0.0.1 gebunden sind, erreicht der Container dagegen nicht. Ein internes Netz ändert daran nichts. Es nimmt nur den Weg ins Internet weg.
Was ich daraus mitnehme
Die Mauer ist die Schicht, die am wenigsten vom Verhalten des Agenten abhängt. Gewissen und Türsteher urteilen. Die Mauer urteilt nicht, sie ist einfach da. Was nicht eingehängt ist, kann kein Agent erreichen, egal wie geschickt er formuliert wurde.
Gleichzeitig ist sie die Schicht, die am meisten Pflege braucht. Images neu bauen, Logins erneuern, Tokens rotieren, Netzprobleme beheben. Wer sie einsetzt, betreibt ein kleines System mehr.
Für Agenten, die unbeaufsichtigt arbeiten, ist mir das den Aufwand wert.
Was du jetzt tun kannst
- Prüf, was deine Agenten-Umgebung vom Mac sieht. Ein
ls /Usersim Container oder in der VM sagt es dir sofort. - Gib dem Agenten keinen Root. Ein eigener Benutzer im Image kostet eine Zeile.
- Schneide die Schlüssel klein. Eigenes Konto, ein Deploy-Key pro Repository, Token mit Ablaufdatum.
- Probier Apples
containeraus, wenn du einen Mac mit Apple Silicon und macOS 26 hast. Die Images sind dieselben wie bei Docker.