OpenHands auf Linux (Ubuntu 26.04, headless): Setup und Konfiguration
Ziel: OpenHands mit Mistral-Backend auf einem headless Ubuntu-Server (Version 26.04) betreiben. Gleiche Architektur wie unter Windows (siehe 02 OpenHands auf Windows - Setup und Konfiguration), aber ohne Desktop, ohne WSL, mit SSH-Zugriff und härterer Netzwerk-Absicherung. Docker läuft rootless: Daemon und alle Container laufen im User-Namespace deines Benutzerkontos — Docker-Zugriff kann niemals zu root auf dem Host eskalieren, deshalb kommt dein Benutzer nicht in die docker-Gruppe (die wäre root-äquivalent).
Unterschiede zur Windows-Anleitung
Aspekt
Windows
Ubuntu headless
Docker
Docker Desktop + WSL2-Integration
Docker Engine rootless (Pakete via apt, Daemon als User-Service)
Zugriff auf geteilten Ordner
Explorer über \\wsl$\… (Windows) bzw. direkt ~/agent
SSH/SCP, SFTP, oder Git
UI-Erreichbarkeit
http://localhost:8000 am eigenen Rechner
ssh -L 8000:localhost:8000 (SSH-Tunnel)
Sicherheit
Loopback-Binding reicht meist
zusätzlich: kein root im Docker-Pfad, Firewall, kein Port nach außen
Wichtig: Der klassische Schritt „Benutzer in die docker-Gruppe aufnehmen” entfällt bei Rootless Docker bewusst — Mitglieder der docker-Gruppe sind faktisch root. Der Rootless-Daemon braucht weder docker-Gruppe noch sudo im Betrieb.
Ubuntu 24.04+/26.04: AppArmor und unprivilegierte User-Namespaces
Ubuntu schränkt unprivilegierte User-Namespaces ein (kernel.apparmor_restrict_unprivileged_userns = 1), was Rootless Docker grundsätzlich blockieren würde. Bei Installation über die dpkg-Pakete (oben) liefert Ubuntu das nötige AppArmor-Profil für rootlesskit gleich mit (/etc/apparmor.d/rootlesskit) — es ist kein manueller sysctl- oder Profil-Eingriff nötig. Nur bei der Skript-Installation über get.docker.com/rootless müsste dieses Profil selbst angelegt werden.
Danach den Rootless-Daemon als dein Benutzer einrichten:
# Voraussetzung: Sub-UID/GID-Bereiche müssen existieren (je mindestens 65536)grep ^$USER: /etc/subuid /etc/subgiddockerd-rootless-setuptool.sh installsudo loginctl enable-linger $USER # einmalig: Daemon startet beim Boot ohne Login-Sitzungsystemctl --user start docker.servicedocker info | grep -A3 'Security Options' # muss "rootless" zeigen
Ab jetzt spricht docker für deinen Benutzer mit dem Rootless-Daemon (CLI-Context rootless). Der rootful-System-Dienst wird nicht mehr gebraucht und kann deaktiviert werden:
~/.openhands ist der persistente State-Ordner (Session-Verlauf, Einstellungen, generierte Backend-Keys) und wird in Schritt 3 in den Container gemountet. Wer einen Geheimnis-Manager nutzt (z.B. Vaultwarden mit CLI-Wrapper), speichert den Key dort statt in der Datei und lädt ihn beim Containerstart in die Umgebung.
Container-Prozesse laufen als UID 0 — im Rootless-User-Namespace entspricht UID 0 exakt deinem Host-Benutzer. Jede andere Container-UID mappt in den Subuid-Bereich (/etc/subuid) und kann nicht in die gemounteten Ordner schreiben.
-e SANDBOX_USER_ID=0
Auch die Session-Sandboxes laufen als Container-Root ⇒ Dateien, die der Agent anlegt, landen auf dem Host als du:du. (Ohne rootless wäre hier 1000 richtig, siehe Anleitung 02.)
-e HOME=/home/openhands
Mit --user 0:0 wäre $HOME sonst /root — der Entrypoint würde den State im Container abseits des Mounts ablegen und er wäre beim Neustart weg.
OpenHands spawnt die Sandbox-Container über den Socket des Rootless-Daemons ($XDG_RUNTIME_DIR/docker.sock, typisch /run/user/<uid>/docker.sock) — nicht den des rootful-System-Daemons.
Modell: mistral-medium-3-5. Die verfügbaren Modelle hängen vom eigenen Key/Plan ab — prüfbar per curl https://api.mistral.ai/v1/models -H "Authorization: Bearer $MISTRAL_API_KEY". (Ältere Varianten dieser Anleitung nutzten devstral-medium-2507; das ist nicht in jedem Plan verfügbar.)
Unterschiede zum Windows-Setup:
-d --restart unless-stopped: läuft dauerhaft im Hintergrund, übersteht Reboots (headless-Betrieb).
-p 127.0.0.1:8000:8000: UI ist nur lokal erreichbar – Zugriff von außen ausschließlich per SSH-Tunnel (Schritt 4).
API-Key wird aus ~/.openhands/mistral.env in die Shell-Umgebung geladen und per -e LLM_API_KEY (ohne Wert) an den Container durchgereicht — steht nie als Klartext in einer Befehlszeile, Datei im geteilten Ordner oder im ~/.profile.
Docker-Socket und Loopback
Der docker.sock-Mount gibt dem Container Kontrolle über den Docker-Daemon. Im Rootless-Betrieb ist das Kontroll-Radius auf dein Benutzerkonto begrenzt (kein host-root) — trotzdem gilt:
Der geteilte Ordner ist schlicht ~/agent — Unterordner wie incoming/ und outgoing/ sind nicht nötig: OpenHands arbeitet direkt im ganzen Ordner, Dateien kommen einfach hinein bzw. liegen nach der Session darin.
# Datei vom Client auf den Serverscp aufgabe.txt <user>@<server-ip>:agent/# Ergebnis zurückholenscp <user>@<server-ip>:agent/antwort.md .
Alternativ (und für Agenten-Aufgaben oft sauberer): Der Agent arbeitet direkt in einem Git-Repo unter ~/agent/repo – Änderungen kommen als Commit/Branch zurück, die du lokal pulst.
Schritt 6: Betrieb und Wartung
# Status/Logsdocker psdocker logs -f openhands# Updatedocker stop openhands && docker rm openhandsdocker pull ghcr.io/openhands/agent-canvas:latest# danach Schritt-3-Befehl erneut ausführen# Aufräumen von gestoppten Sandbox-Containerndocker container prune -f
Persistenz: Session-State in ~/.openhands, Arbeitsdaten in ~/agent – Container löschen kostet keinen Chatverlauf.
Typische Fehler und Lösungen (Linux/rootless-spezifisch)
Problem
Ursache / Lösung
permission denied bei docker
Nicht die docker-Gruppe setzen! Prüfen: Läuft der User-Daemon (systemctl --user status docker.service) und ist der CLI-Context rootless aktiv (docker context show)?
dockerd-rootless startet nicht: fork/exec /proc/self/exe: permission denied
AppArmor-Userns-Beschränkung. Bei dpkg-Installation muss /etc/apparmor.d/rootlesskit existieren; sonst Profil anlegen oder docker-ce-rootless-extras per apt installieren
UI im Browser des Clients nicht erreichbar
Tunnel fehlt oder falsch: ssh -L 8000:localhost:8000 muss laufen; Server bindet nur 127.0.0.1
Sandboxes starten nicht
Socket-Mount fehlt oder zeigt auf den falschen Socket — im Rootless-Betrieb muss $XDG_RUNTIME_DIR/docker.sock gemountet werden; docker logs openhands zeigt Details
Agent kann nicht auf /projects schreiben
--user 0:0 / SANDBOX_USER_ID=0 gesetzt? Ohne die Flags mappt die Container-UID in den Subuid-Bereich und hat keinen Zugriff auf die Host-Mounts
Nach Reboot ist alles weg
--restart unless-stopped gesetzt? loginctl enable-linger $USER aktiviert (sonst startet der Rootless-Daemon erst mit einer Login-Sitzung)? Persistenz-Mounts (~/.openhands, ~/agent) vorhanden?
Verdacht auf Missbrauch von außen
ss -tlnp prüfen: Port 8000 darf nur auf 127.0.0.1 lauschen; UFW-Regeln kontrollieren