← swarm-exec.cloud-surfers.net

swarmexec_ Doku

Den Client bedienen — Befehle, Flags, Konfiguration & die TUI.

swarmexec bietet dir docker exec -it, Logs, Port-Forwarding und Volume-Verwaltung für jeden Container in einem Docker Swarm, aus einem einzigen Terminal. Ein kleiner Agent pro Node (ein globaler Swarm-Service) erledigt die Arbeit auf seinem Node; der Client fragt den Swarm-Manager, welcher Node dein Ziel ausführt, und verbindet sich dann direkt mit dem Agent dieses Nodes über mTLS. Diese Seite dokumentiert den Client.

1. Wie es funktioniert

Zwei Binaries, ein Wire-Protokoll:

operator terminal ──gRPC/mTLS──> agent(nodeN) ──docker.sock──> container └── Docker manager API (resolve node + container id)

Die Manager-Verbindung nutzt deinen Docker-CLI-Context (sie berücksichtigt also --context, ssh://-Bastions, mTLS usw.). Die Agent-Verbindung wird mit mutual TLS authentifiziert, oder mit einem geteilten Secret gegen einen selbstsignierten Agent — siehe Authentifizierung.

2. Installation

Setzt Docker Engine 19.03 oder neuer voraus (API 1.40) — auf dem Manager und auf jedem Node. swarmexec lehnt einen älteren Daemon schon beim Verbinden ab und nennt dabei beide Versionen, statt sich zu verbinden und dann eine Ansicht nach der anderen scheitern zu lassen. Zwei Funktionen brauchen vom Daemon des Nodes etwas mehr: die Live-Auslastung und die Image-Ansicht pro Node wollen Docker 23.0 (API 1.41/1.42) — darunter bleiben sie leer und sagen das auch, statt zu scheitern.

Der Client ist ein einzelnes statisches Binary. Hol dir den aktuellen Release-Build für deine Plattform:

$ curl -fsSLo swarmexec \ https://gitlab.logle.io/cs-public/swarm-remote-exec/-/releases/permalink/latest/downloads/bin/swarmexec-linux-amd64 $ chmod +x swarmexec && sudo mv swarmexec /usr/local/bin/ $ swarmexec --version

Builds: linux-amd64, linux-arm64, darwin-arm64, darwin-amd64, windows-amd64. Das Agent-Image ist öffentlich auf Docker Hub als logleio/swarmexec-agent verfügbar.

3. Schnellstart

Richte deinen Docker-Context auf einen Swarm-Manager aus und provisioniere die Agents dann einmalig. init erzeugt ein geteiltes Secret, rollt den Agent auf jedem Node im selbstsignierten Modus aus und schreibt eine passende Client-Konfiguration — damit die übrigen Befehle sofort funktionieren.

$ swarmexec --context my-swarm init # Agents auf jedem Node ausrollen (einmalig) $ swarmexec ps # Tasks auflisten und den Node, auf dem jede läuft $ swarmexec exec web -- sh # Shell in Service "web", beliebiger Node $ swarmexec logs -f web # den Logs eines Service folgen $ swarmexec port-forward db 5432 # localhost:5432 → container:5432 $ swarmexec ui # interaktive TUI

Baue die Agents wieder mit swarmexec down ab (deine Client-Konfiguration bleibt dabei unangetastet).

4. Ziel-Selektoren

exec, logs und port-forward nehmen ein Ziel als erstes Argument. Es wird in dieser Reihenfolge aufgelöst:

FormBeispielBedeutung
servicewebDie laufende Task des Service. Bei mehr als einem Replica ist das mehrdeutig — siehe unten.
service.slotweb.2Ein bestimmter Replica-Slot. Während eines Rolling Update gewinnt die neueste Task im Slot.
task-idxxh8k1…Eine Swarm-Task-ID.
container-id3f9a2b…Ein Container-ID-Präfix. Braucht einen Node: gib --node an, oder er wird durch Scannen der laufenden Tasks gefunden.

Mehrdeutigkeit. Ein bloßer Service-Name mit mehreren Replicas lässt sich nicht auf eine einzelne Task auflösen. exec fordert dich interaktiv auf, aus einer nummerierten Liste zu wählen; logs und port-forward fragen nie nach — sie geben die Kandidatenliste aus und beenden sich. Vereindeutige mit einem Slot (web.0, web.1, …).

--node wird nur für container-id-Ziele herangezogen und kann ein Hostname, eine IP oder eine Node-ID sein. Service-/Slot-/Task-Ziele werden über die Manager-API lokalisiert und brauchen es daher nie.

5. Konfiguration

Einstellungen kommen aus vier Ebenen, jede überschreibt die vorherige:

built-in defaults config file environment command-line flags

Ein Flag überschreibt nur, wenn du es tatsächlich angibst, sodass ein Wert aus der Datei oder der Umgebung erhalten bleibt, solange er nicht ausdrücklich überschrieben wird.

Konfigurationsdatei

Der Standardpfad ist der erste der folgenden, der gesetzt ist:

  1. $SWARMEXEC_CONFIG
  2. $XDG_CONFIG_HOME/swarmexec/config.yaml
  3. ~/.config/swarmexec/config.yaml

Überschreibe ihn mit --config <path>. Die Datei ist YAML; eine fehlende Datei ist in Ordnung, eine fehlerhafte ist ein Fehler. Sie wird mit 0600 geschrieben (sie kann das geteilte Secret enthalten). Die Log-Datei liegt standardmäßig daneben — ~/.config/swarmexec/swarmexec.log — sofern du nicht --log-file setzt. Schlüssel:

SchlüsselTypBedeutung
castringCA-Zertifikat, das das Serverzertifikat des Agents verifiziert (mTLS)
certstringClient-Zertifikat — sein CN ist deine Operator-Identität
keystringprivater Client-Schlüssel
portintAgent-Port (Standard 9443)
addr_modestringhostname (Standard) oder ip — wie ein Node angewählt wird
server_namestringüberschreibt den TLS-Servernamen, der zur Verifizierung des Agents verwendet wird
agent_secretstringgeteiltes Secret für einen selbstsignierten Agent
agent_secret_filestringliest das Secret aus dieser Datei (hat Vorrang vor agent_secret)
insecureboolüberspringt die Prüfung des Agent-Serverzertifikats (selbstsignierte Agents)
legacy_secretboolsendet zusätzlich das rohe Secret, für Agents älter als v1.17.3 — standardmäßig aus; siehe unten
operatorstringAudit-Identität, wenn kein Client-Zertifikat verwendet wird (Standard: OS-Benutzername)
logs.formatstringStandard-Log-Format für logs und die TUI: classic | json | logfmt | gelf | raw (leer = classic)
logs.min_levelstringStandard-Mindeststufe: trace..fatal (weglassen für keinen Stufenfilter)
ui.dimfloatwie stark der Hintergrund hinter einem offenen Overlay abgedunkelt wird, ein Bruch 01 (Standard 0.6; 0 = keine Abdunklung)

Der logs:-Abschnitt legt Standardwerte für formatbewusstes Log-Parsing und -Filtern fest; die Flags --log-format / --min-level des logs-Befehls überschreiben sie:

logs: format: json # classic | json | logfmt | gelf | raw min_level: warn # trace..fatal; omit for no level filter

Inspiziere die effektive, zusammengeführte Konfiguration (Secret maskiert) mit:

$ swarmexec config show # or: --json

addr-mode

hostname (Standard) wählt den gemeldeten Hostnamen des Nodes an; ip wählt seine angekündigte Adresse an. Verwende ip, wenn die Node-Hostnamen von deiner Workstation aus nicht auflösbar sind — init schreibt genau deshalb addr_mode: ip in die generierte Konfiguration. Der Swarm-Leader meldet 0.0.0.0 für sich selbst; der Client stellt seine echte Adresse automatisch aus der Raft-Peer-Liste wieder her.

Umgebungsvariablen

VariableSetzt
SWARMEXEC_CONFIGPfad der Konfigurationsdatei
SWARMEXEC_CA / _CERT / _KEYmTLS-Material
SWARMEXEC_PORTAgent-Port
SWARMEXEC_ADDR_MODEhostname / ip
SWARMEXEC_SERVER_NAMETLS-Servername
SWARMEXEC_AGENT_SECRET / _FILEgeteiltes Secret / Secret-Datei
SWARMEXEC_INSECUREPrüfung überspringen (1/true/yes/on)
SWARMEXEC_OPERATORAudit-Identität
SWARMEXEC_UI_DIMOverlay-Hintergrundabdunklung (ui.dim)
SWARMEXEC_KEYSPfad zur Tastenbelegungsdatei der TUI (Standard: keys.yaml neben der Konfiguration)
SWARMEXEC_SSH_MULTIPLEX0/off/false/no schaltet geteilte ssh-Verbindungen ab (siehe Gemeinsame ssh-Verbindungen)
DOCKER_CONTEXTDocker-Context für die Manager-API
XDG_CONFIG_HOMEBasis für den Standard-Konfigurationspfad

6. Authentifizierung

Der Client authentifiziert sich gegenüber dem Agent in einem von zwei Modi.

Modus A — mutual TLS (Standard)

Setze ca, cert und key (alle drei erforderlich). Der Client verifiziert den Agent gegen deine CA und präsentiert sein Zertifikat; der Agent autorisiert und auditiert dich anhand des CN des Zertifikats. Das ist der Standard und die empfohlene Vorgehensweise.

$ swarmexec --ca ca.crt --cert me.crt --key me.key ps

Modus B — selbstsignierter Agent + geteiltes Secret

Einfacher zu betreiben (ein Secret, keine PKI) — das richtet init ein. Setze agent_secret (oder agent_secret_file) und entweder eine ca, um den Agent zu verifizieren, oder insecure: true, um die Verifizierung zu überspringen. Ein Client-Zertifikat ist optional (aber cert und key müssen gemeinsam gesetzt oder beide leer sein). Deine Audit-Identität ist der Wert von operator (Standard: dein OS-Benutzername).

$ swarmexec --agent-secret "$SECRET" --insecure ps
insecure überspringt die Prüfung des Serverzertifikats des Agents. Das Secret ist dadurch nicht gefährdet: Der Client sendet es nie, sondern nur einen Beweis aus Secret und dem Zertifikat der Verbindung, auf der er reist — ein nicht verifizierter Server erhält also nichts, was gegen einen echten Agent funktioniert. Es bleibt: Der Agent ist dir gegenüber nicht authentifiziert — wer antwortet, liest mit, was diese Sitzung ihm schickt. In einem Netz, dem du nicht traust, nimm eine ca, und halte das Secret aus deiner Shell-History heraus (verwende agent_secret_file oder die Konfigurationsdatei).

Umstieg auf v1.17.3 — zuerst die Agents aktualisieren

Ein v1.17.3-Client kann sich bei einem älteren Agent nicht authentifizieren. Er sendet nur noch den verbindungsgebundenen Beweis, und Agents vor v1.17.3 kennen diese Form nicht — sie antworten invalid or missing agent secret, obwohl dein Secret stimmt. Aktualisiere sie zuerst:
$ swarmexec init --force
Solange die Flotte gemischt ist, sendet legacy_secret: true in der Client-Konfiguration zusätzlich das rohe Secret — mit der oben beschriebenen Preisgabe. Entferne es, sobald die Agents aktuell sind, und setze am Agent -allow-legacy-secret=false, damit kein Client das Credential versehentlich aufs Kabel legen kann.

7. Docker-Context & SSH

--context wählt den Docker-CLI-Context, der für die Manager-API verwendet wird. Auflösungsreihenfolge: --context$DOCKER_CONTEXT$DOCKER_HOST → der aktive Context in ~/.docker/config.json → der lokale Socket unix:///var/run/docker.sock.

Kein lokales Docker erforderlich. Der Client führt nie das docker-Binary aus — er bindet das Docker-Go-SDK ein und spricht die Manager-API direkt, wobei er alle Context-Metadaten selbst aus Dateien liest. Alles, was er braucht, ist ein erreichbarer Swarm-Manager-Endpoint (die Node-Auflösungsaufrufe funktionieren nur gegen einen Manager):
  • $DOCKER_HOST auf einem entfernten tcp://-Manager (mTLS) — dann ist auf deiner Workstation überhaupt kein Docker installiert;
  • ein ssh://-Context — braucht den ssh-Client (nicht docker); sowohl der Manager- als auch der Agent-Verkehr tunneln darüber;
  • der lokale Socket unix:///var/run/docker.sock — nur der Standard-Fallback und die einzige Option, die einen lokalen Daemon voraussetzt.
Die docker-CLI selbst wird nur benötigt, um benannte Contexts zu erstellen (docker context create) — oder erstelle sie mit swarmexec context create <name> --docker-host …, sodass die docker-CLI selbst dafür nicht mehr benötigt wird; verwende $DOCKER_HOST, um benannte Contexts ganz zu überspringen. (init liest lokale docker login-Anmeldedaten nur für ein privates Agent-Image — nicht für das öffentliche Standard-Image.)

SSH-Bastion. Wenn der Context-Host ein ssh://-Endpoint ist, wird die Manager-API über SSH getunnelt — und die Agent-Verbindung ebenfalls: Da die node:9443-Endpoints der Nodes von deiner Workstation aus meist nicht routbar sind, tunnelt der Client den gRPC-Verkehr des Agents automatisch über denselben SSH-Host. Keine zusätzlichen Flags nötig.

$ docker context create swarm --docker host=ssh://ops@manager.example.com $ swarmexec --context swarm exec web -- sh

8. Globale Flags

Diese persistenten Flags gelten für jeden Befehl:

FlagStandardBeschreibung
--configPfad der Konfigurationsdatei (Standard ~/.config/swarmexec/config.yaml)
--contextDocker-Context für die Manager-API; unterstützt ssh:// (auch $DOCKER_CONTEXT)
--port9443Agent-Port
--addr-modehostnameNode-Anwähladresse: hostname | ip
--caCA-Zertifikat zur Verifizierung des Agents (mTLS)
--certClient-Zertifikat (mTLS; CN ist die Operator-Identität)
--keyprivater Client-Schlüssel (mTLS)
--server-nameüberschreibt den TLS-Servernamen für die Agent-Verifizierung
--agent-secretgeteiltes Secret für einen selbstsignierten Agent
--agent-secret-fileDatei, aus der das geteilte Secret gelesen wird
--insecurefalsePrüfung des Agent-Serverzertifikats überspringen
--operatorOS-Benutzernamefür das Audit gemeldete Operator-Identität
--log-levelinfoLog-Ausführlichkeit: debug | info | warn | error | off (off deaktiviert das Logging vollständig)
--log-filePfad der Log-Datei (Standard swarmexec.log neben der Konfigurationsdatei)
--infozeigt Version, Lizenz und Kontaktinfo
--versiongibt die Client- und Protokollversion aus
Logging. swarmexec loggt in strukturierter Form (Go slog, Textformat) in die Log-Datei und einen In-Memory-Ringpuffer. In der TUI werden Logs nie ins Terminal geschrieben (das würde die Anzeige beschädigen) — stattdessen speist der Ringpuffer einen Live-Viewer in der App (siehe Die TUI). --log-level off deaktiviert das Logging vollständig; eine Log-Datei, die nicht geöffnet werden kann, ist nicht fatal (es wird dann nur auf den Ringpuffer zurückgegriffen).

9. Befehle

init — die Agents provisionieren

Rollt den Agent als globalen Swarm-Service über die Manager-API aus: erstellt ein Docker-Secret mit geteiltem Secret, führt den Agent im selbstsignierten Modus auf jedem Node aus (Host-Port 9443) und schreibt die passende Client-Konfiguration. Führe es einmal pro Swarm aus.

swarmexec init
FlagStandardBeschreibung
--imagedocker.io/logleio/swarmexec-agent:latestauszurollendes Agent-Image
--secretzufälligzu verwendendes geteiltes Secret (Standard: ein zufälliges erzeugen)
--service-nameswarmexec_agentName für den Agent-Service
--port9443Host-Port, den der Agent veröffentlicht
--forcefalseden Service aktualisieren, falls er bereits existiert
--save-configtruedie Client-Konfiguration schreiben
--registry-authtruelokale Registry-Anmeldedaten weitergeben, damit Nodes ein privates Image pullen können
--waittruewarten, bis die Agents hochkommen, und Fortschritt melden
--rollout-timeout90swie lange auf den Start der Agents gewartet wird
Das Standard-Agent-Image ist öffentlich auf Docker Hub, es ist also kein Registry-Login nötig. Führe es mit --force erneut aus, um eine neue Agent-Version auszurollen — siehe Agents neu ausrollen, ohne das Secret zu ändern.

down — die Agents entfernen

Das Gegenstück zu init: entfernt den globalen Agent-Service und standardmäßig das Docker-Secret mit dem geteilten Secret. Es rührt deine Client-Konfiguration nicht an.

swarmexec down
FlagStandardBeschreibung
--service-nameswarmexec_agentName des zu entfernenden Agent-Service
--keep-secretfalsedas Docker-Secret mit dem geteilten Secret nicht entfernen
-y, --yesfalsenicht zur Bestätigung auffordern

doctor — den Swarm diagnostizieren

Prüft die Manager-Verbindung, ob der Agent-Service ausgerollt ist, und testet den Agent jedes bereiten Nodes auf Erreichbarkeit und Versionsabweichung. Gibt eine Tabelle pro Node aus (NODE  AGENT  VERSION  PROTO); beendet sich mit einem Wert ungleich null, wenn der Manager nicht erreichbar ist oder ein Agent unhealthy ist. Ein Node mit dem Status too old (init --force) betreibt einen Agent, der älter ist als dein Client — siehe Agents neu ausrollen, ohne das Secret zu ändern.

swarmexec doctor
FlagStandardBeschreibung
--connect-timeout10sConnect-Timeout pro Node
--jsonfalseJSON statt einer Tabelle ausgeben

security report — ein Markdown-Bericht über den ganzen Cluster

Das Sicherheitsrisiken-Overlay prüft Service-Specs und zeigt das Ergebnis auf dem Bildschirm. Dieser Befehl schreibt dieselbe Prüfung — plus die Checks, die dem Cluster gehören und keinem einzelnen Service — als Markdown heraus. So lässt sie sich fern vom Terminal lesen, an ein Ticket hängen oder neben die Stack-Dateien committen und von Release zu Release diffen. Die Ausgabe geht nach stdout, solange kein -o angegeben ist, ist also genauso gut pipebar wie speicherbar.

swarmexec security report -o security.md

Vier Prüfungen laufen auf Cluster-Ebene, von denen keine ein Service-Spec beantworten kann:

PrüfungSchweregradWas sie meldet
network-unencrypted — „Overlay-Verkehr ist nicht verschlüsselt“mediumein Overlay-Netz, das Service-Verkehr trägt, ohne Verschlüsselung der Datenebene. Swarm tunnelt Container-Verkehr zwischen Nodes im Klartext über VXLAN, solange das Netz nicht mit --opt encrypted angelegt wurde — und am laufenden Service sieht man so oder so keinen Unterschied. Netze ohne angehängte Services werden nicht gemeldet (sie tragen nichts), und ingress ebenso wenig: es lässt sich gar nicht verschlüsseln, ein Befund darauf wäre nie abzustellen
autolock-disabled — „Manager sind nicht autolocked“mediumder Raft-Speicher der Manager ist nicht im Ruhezustand verschlüsselt. Er enthält jedes Secret, jede Config und den CA-Schlüssel des Clusters, und sein Schlüssel liegt auf derselben Platte — wer die Platte eines Managers mitnimmt, hat alles. Einschalten mit docker swarm update --autolock=true, und den Unlock-Key aufbewahren: ein neu gestarteter Manager fragt danach. Ist die Swarm-Konfiguration nicht lesbar, meldet der Bericht autolock-unknown statt zu raten — „aus“ anzunehmen erfände einen Befund, „an“ wäre eine Falschentwarnung
agent-proto-mismatch / agent-version-skew / agent-too-old / agent-unreachablehigh / medium / lowder Agent ist das, was bei jedem Exec, jedem Log und jedem Port-Forward die Autorisierung durchsetzt — ein Agent, der älter ist als dein Client, setzt womöglich eine Regel nicht durch, die der Client voraussetzt. Ein Protokoll-Mismatch ist high und wiegt schwerer als eine Versionsdifferenz: die beiden Seiten sind sich über den Vertrag selbst uneinig, nicht bloß über den Build. Ein Agent, der nicht geantwortet hat, wird als Lücke ausgewiesen, nicht als bestanden. Für einen unveröffentlichten (dev) Client wird kein Skew gemeldet — er weicht von jedem Release-Agenten naturgemäß ab, dieselbe Ausnahme macht doctor
unused-secret / unused-configlowein Secret oder eine Config, auf die kein Service verweist. Sie wird weiterhin über den Raft-Speicher verteilt und ist für alles lesbar, was einen Manager erreicht — meist ein Zugangsdatum, das jemand rotiert und nie entfernt hat. Der alte Wert ist also noch im Cluster, lange nachdem alle ihn für weg halten

Was der Bericht über sich selbst sagt. Er wird fern vom Terminal gelesen, von jemandem, der beim Lauf nicht dabei war — also trägt er seinen Kontext mit: welcher Cluster, wann, mit welchem Build. Außerdem hat er einen Abschnitt Not covered — nicht erreichte Agenten, unlesbare Swarm-Konfiguration, leere Node-Liste —, denn Schweigen in einem Sicherheitsbericht liest sich als Entwarnung für Boden, den er nie betreten hat. Die Reihenfolge hängt allein von den Befunden ab, zwei Berichte eines unveränderten Clusters unterscheiden sich also nur im Zeitstempel und lassen sich gegeneinander diffen — was nur deshalb etwas wert ist, weil ein fehlender Befund nicht gefunden heißt und nie nicht geschaut.

Die Datei wird 0600 geschrieben, eventuell nötige Elternverzeichnisse 0700. Sie nennt jeden Service, jedes Netz und jedes Secret des Clusters samt jeder gefundenen Schwäche — das ist eine Angriffskarte, und die hat nichts in einer für jedes Konto der Maschine lesbaren Datei verloren.

FlagDefaultBeschreibung
-o, --outputstdoutin diese Datei schreiben statt nach stdout
--connect-timeout10sVerbindungs-Timeout pro Node beim Prüfen der Agents
--skip-agentsfalsedie Node-Agents nicht kontaktieren. Schneller; der Agent-Skew steht dann unter Not covered, statt stillschweigend zu fehlen

Denselben Bericht schreibt auch das TUI: w im Sicherheitsrisiken-Overlay. Er wird dabei frisch clusterweit erhoben, ist also kein Abzug dessen, was das Overlay zeigt, und läuft außerhalb der UI-Goroutine — die Oberfläche bleibt bedienbar, während er jeden Node abfragt.

stack export / stack diff — ein deployter Stack als Datei

stack export liest einen deployten Stack aus dem Cluster zurück und schreibt ihn als Compose-förmiges YAML. stack diff vergleicht eine Stack-Datei mit dem, was tatsächlich läuft, und gibt einen Unified Diff aus: eine +-Zeile ist etwas, das ein Deploy der Datei hinzufügen würde, eine --Zeile etwas, das es entfernen würde. Wie git diff --exit-code endet der Befehl mit 1, sobald es Unterschiede gibt — also auch in CI brauchbar.

swarmexec stack ls swarmexec stack export postgres -o postgres.yml swarmexec stack diff postgres.yml postgres

Im TUI bietet E auf dem Stacks/Services-Tab beides für den Stack unter dem Cursor an — die Stack-Zeile, ein Service darin oder einer seiner Container.

Warum kein Textvergleich. Ein deployter Stack trägt Dinge, die die Datei nie hatte: den Image-Digest, den der Daemon beim Deploy aufgelöst hat, seine eigenen Defaults für Restart- und Update-Policy, und den Stack-Namespace vor jedem Netz, Secret und Volume. Beide als Text zu vergleichen meldet Dutzende Unterschiede für einen Stack, der exakt synchron ist — und das ist schlimmer als gar kein Werkzeug, weil man lernt, die Ausgabe zu ignorieren. Stattdessen läuft die Datei durch Dockers eigenen Compose-Loader und -Converter, denselben Code, den docker stack deploy nutzt, und beide Seiten werden anschließend von derselben Funktion reduziert. Beantwortet wird die Frage „würde ein Deploy dieser Datei etwas ändern?“, nicht „sind diese beiden Dateien gleich geschrieben?“. Daemon-Defaults fallen auf beiden Seiten weg; ein Wert, der nicht der Default ist, erscheint weiterhin — es wird also nichts Echtes verborgen.

Ein Export ist eine Beschreibung, kein Backup. Der Wert eines Secrets ist in der Engine-API nur schreibbar und nie wieder auslesbar, und Volume-Inhalte liegen auf den Nodes. Beides wird deshalb als external deklariert, und die exportierte Datei sagt das in ihrem eigenen Kopf: den Stack anderswo neu aufzubauen heißt, zuerst die Secrets anzulegen. Spec-Felder, die das Rendering nicht trägt (tty, ulimits, seccomp- und AppArmor-Einstellungen und einige weitere), stehen an derselben Stelle — denn der gefährliche Fehler eines Vergleichswerkzeugs ist nicht die falsche Antwort, sondern das selbstsichere Schweigen. „Keine Unterschiede“ kommt daher immer mit seinen Vorbehalten.

Variablen werden aus deiner Umgebung interpoliert, genau wie bei docker stack deploy. Eine Datei mit ${TAG} beschreibt also für ein anderes TAG einen anderen Stack, und der Diff hängt von der Umgebung ab, in der er läuft — was richtig ist: alles andere würde „keine Änderung“ melden für ein Deploy, das das Image austauscht.

BefehlBeschreibung
stack lsdie auf dem Cluster deployten Stacks
stack export <stack>den Stack als Compose-YAML schreiben; mit -o in eine Datei, sonst nach stdout
stack diff <datei> [stack]eine Datei mit dem deployten Stack vergleichen. Der Stack-Name ist standardmäßig der Dateiname ohne Endung; --stack oder ein zweites Argument überschreibt ihn. Endet bei jedem Unterschied mit 1

stack deploy — eine Stack-Datei einspielen, nach Prüfung

Lädt eine Stack-Datei, lässt die Sicherheitsprüfungen über das laufen, was tatsächlich ausgerollt würde, und spielt sie ein. Was das gegenüber docker stack deploy wertvoll macht, ist der Gate: die Datei wird geprüft, bevor irgendetwas angelegt ist, und wenn etwas oberhalb von „informativ“ gefunden wird, hält der Deploy an und fragt.

swarmexec stack deploy web.yml # fragt, wenn etwas gefunden wurde swarmexec stack deploy web.yml --check # nur prüfen, nie ausrollen swarmexec stack deploy web.yml --force # trotz Befunden ausrollen, bewusst

Im TUI macht EDeploy a file dasselbe: zuerst die Befunde, d fährt fort, Esc geht weg. Zu diesem Zeitpunkt ist nichts angelegt — genau dafür ist die Pause da.

Die Prüfungen sind kein zweiter Regelsatz. Die Datei wird in genau die ServiceSpec-Werte umgewandelt, die der Deploy einreichen würde, und darauf laufen die vorhandenen acht Analyzer — dieselben Prüfungen, die im Baum den Schild setzen und das !-Overlay sowie den Security-Report füllen. Zwei Sätze würden auseinanderdriften: eine Regel, die an einer Stelle verschärft wird und an der anderen nicht, heißt, dass der Gate eine Datei durchlässt, die der Baum im selben Moment markiert, in dem sie läuft — und wem „nichts gefunden“ gesagt wurde, dem wurde etwas Falsches gesagt. Außerdem sehen die Prüfungen so, was der Cluster tun wird, nicht was die Datei sagt; Composes Defaults und Kurzschreibweisen liegen dazwischen.

--yes heißt nicht „ignoriere die Befunde“. Es heißt „frag nicht“, und ein Lauf, der etwas oberhalb von „informativ“ findet, verweigert trotzdem und endet mit Exit ungleich null. Alles andere würde den Gate zur Formalie machen, sobald ihn jemand in CI einbaut. Um trotz Befunden auszurollen, muss man --force schreiben — anderer Tastendruck, andere Rechtfertigung hinterher. Ein nicht-interaktiver Lauf ohne Antwort auf stdin zählt als Nein.

FlagBeschreibung
--checknur prüfen und anhalten. Endet mit 1, wenn etwas oberhalb von „informativ“ gefunden wurde — damit lässt sich eine Pipeline absichern
-y, --yesnicht fragen. Rollt aus, wenn die Prüfungen sauber sind; verweigert, wenn nicht
--forcetrotz Befunden ausrollen, ohne Rückfrage
--pruneServices des Stacks entfernen, die die Datei nicht mehr deklariert. Standardmäßig aus, wie bei docker: eine Datei, die nur ein Teil des Stacks ist, ist weit öfter ein Versehen als eine Löschanweisung

Was ein Deploy tut, in dieser Reihenfolge: Netze, dann Secrets, dann Configs, dann Services — ein Service, der auf etwas noch nicht Vorhandenes verweist, scheitert, und der Stack bleibt halb angewandt zurück. Externe Netze werden zuerst geprüft, denn ein nicht existierendes ist der häufigste Grund, warum ein Deploy auf halber Strecke stehen bleibt. Ein vorhandenes Secret wird nie überschrieben: sein Wert ist in Swarm unveränderlich, ein Secret zu ändern heißt, ein neues unter neuem Namen anzulegen. Scheitert ein Deploy doch mittendrin, wird das bereits Angewandte zusammen mit dem Fehler gemeldet, statt es erraten zu lassen.

Agents neu ausrollen, ohne das Secret zu ändern

Früher oder später musst du die Agents in einem bereits provisionierten Swarm neu ausrollen: ein Agent hängt, doctor zeigt einen Node als too old (init --force), ein Kommando scheitert mit „agent is older than this client (missing RPC) — update it with swarmexec init --force, oder ein Node wurde neu aufgesetzt und sein Agent kam nie zurück. Die Lösung ist, init mit --force erneut auszuführen — das Flag bedeutet genau den Service aktualisieren, falls er bereits existiert. Ohne das Flag ist ein init auf einen bestehenden Service ein Usage-Fehler, der dich auffordert, --force zu ergänzen; geändert wird dabei nichts.

$ swarmexec init --force

Damit wird der Agent auf jedem Node neu ausgerollt, und das geteilte Secret bleibt unangetastet. Docker-Secrets sind unveränderlich, also findet init das vorhandene swarmexec_agent_secret, meldet es als reusing existing und behält den Wert, der bereits im Cluster liegt. Jeder Client, der vorher funktioniert hat, funktioniert danach weiter — es muss nichts neu verteilt werden.

Übergib beim erneuten Ausrollen kein --secret. Existiert das Secret bereits, kann es nicht überschrieben werden: dein Wert wird nicht auf den Cluster angewendet — aber er wird trotzdem in deine Client-Konfiguration geschrieben. Ist er nicht zufällig der echte Wert des bestehenden Secrets, weisen die Agents dich ab dem Moment zurück, und der Fehler zeigt sich erst später und sieht nach einem defekten Agent aus statt nach einer falschen lokalen Konfiguration. init warnt in genau diesem Fall — nimm die Warnung ernst. Um das Cluster-Secret wirklich zu ändern, musst du das Secret zuerst entfernen (kein Service darf es referenzieren) und init erneut ausführen.

--save-config steht standardmäßig auf true, ein erneutes Ausrollen schreibt also auch ~/.config/swarmexec/config.yaml neu. Übergib --save-config=false, wenn die lokale Client-Konfiguration unberührt bleiben muss — etwa wenn du die Agents von einem Rechner aus neu ausrollst, dessen Konfiguration bereits korrekt ist, oder aus der CI.

$ swarmexec init --force --save-config=false

Danach prüfen: doctor meldet einen Status pro Node; ok auf jedem Node heißt, dass Client, Agent und Secret wieder zusammenpassen.

$ swarmexec doctor

ps — Tasks auflisten

Listet mögliche Tasks/Container und den Node, auf dem jede läuft. Nimmt einen optionalen Service-Filter. Spalten: SERVICE  SLOT  CONTAINER  NODE  IP  UPTIME. Dies spricht nur mit der Manager-API — es funktioniert sogar, bevor die Agents erreichbar sind.

swarmexec ps [service]
FlagStandardBeschreibung
--jsonfalseJSON statt einer Tabelle ausgeben

exec — einen Befehl ausführen / eine Shell öffnen

Exec in einen Container, der irgendwo im Swarm läuft. Ohne Befehl öffnet es /bin/sh. Ein TTY wird automatisch zugewiesen, wenn stdin ein Terminal ist und du keinen Befehl angegeben hast; erzwinge es mit -t. Der eigene Exit-Code des Remote-Befehls wird unverändert weitergegeben.

swarmexec exec [flags] <target> [-- <cmd> [args...]]
FlagStandardBeschreibung
-i, --stdintruestdin offen halten
-t, --ttyautoein TTY zuweisen (auto: true genau dann, wenn stdin ein Terminal und kein Befehl ist)
-u, --userBenutzername oder UID (z. B. 1000:1000)
-w, --workdirArbeitsverzeichnis im Container
-e, --envUmgebungsvariablen setzen (KEY=VALUE, wiederholbar)
--nodeNode-Hinweis/-Override für container-id-Ziele
--connect-timeout10sTimeout für die Verbindung zum Agent
$ swarmexec exec web -- sh $ swarmexec exec web.2 -- cat /etc/hostname # ein bestimmtes Replica $ swarmexec exec -u root -w /app web -- ls -la $ swarmexec exec -e FOO=bar web -- env

logs — Logs streamen

Streame die Logs eines Containers von irgendwo im Swarm.

swarmexec logs [flags] <target>
FlagStandardBeschreibung
-f, --followfalseneue Log-Zeilen weiter streamen
--tail0Anzahl der Zeilen vom Ende, mit denen begonnen wird (0 = alle)
-t, --timestampsfalsejeder Zeile einen Zeitstempel voranstellen
--since0nur Logs neuer als dies (z. B. 10m, 1h)
--log-formatclassicZeilen parsen als classic | json | logfmt | gelf | raw (Standard aus der Konfiguration, sonst classic)
--min-levelnur diese Stufe und höher anzeigen: trace | debug | info | warn | error | fatal
--grepnur Zeilen anzeigen, deren (geparste) Nachricht diesem Go-Regexp entspricht
--nodeNode-Hinweis/-Override für container-id-Ziele
--connect-timeout10sTimeout für die Verbindung zum Agent

Formatbewusstes Parsen & Filtern. --log-format sagt dem Client, wie er jede Zeile lesen soll, damit er eine Stufe und eine Nachricht herausziehen kann: classic extrahiert eine Stufe aus einer reinen Textzeile, json parst JSON im Logstash-Stil (level/message-Felder), logfmt parst den key=value-Stil, den viele Go-Anwendungen, der Docker-Daemon und HashiCorp-Tools verwenden (msg/message, level/lvl/severity, ts/time), gelf parst Graylog-GELF-JSON (numerische Syslog-Stufe) und raw reicht Zeilen unverändert durch. --min-level verwirft dann alles unterhalb der gewählten Stufe, und --grep behält nur Zeilen, deren geparste Nachricht dem Regexp entspricht.

Zeilen ohne erkennbare Stufe passieren den --min-level-Filter immer, damit mehrzeilige Stacktraces nicht verloren gehen. Dieselben Standardwerte lassen sich einmalig unter dem logs:-Konfigurationsabschnitt setzen (Flags überschreiben).

Folgen über Container-Ersetzung hinweg. Mit -f auf einem service- oder service.slot-Ziel folgt logs weiter, wenn der gestreamte Container durch ein Rolling Update, einen Neustart oder eine Neuplanung ersetzt wird: es löst den aktuell laufenden Container des Service neu auf (denselben Slot, oder denselben Node bei einem global-Service), verbindet sich automatisch neu — wie docker service logs -f — und gibt eine gedimmte Hinweiszeile aus (container replaced; reconnected to <id> on <node>). Es wartet bis zu ~30s, bis ein Ersatz eingeplant wird, bevor es aufgibt, und stoppt sauber, wenn der Service entfernt wird. Ein bloßes container-id-Ziel hat keinen Nachfolger und stoppt daher einfach wie zuvor.

Die Erkennung liegt im Client: er lernt die Topologie vom Swarm-Manager, mit dem er bereits verbunden ist — die Agents pro Node sind lokal zu ihrem Node und können Ersetzungen auf Swarm-Ebene nicht sehen.
$ swarmexec logs -f --tail 100 web $ swarmexec logs --since 15m -t web $ swarmexec logs --log-format json --min-level warn web $ swarmexec logs --grep 'timeout|refused' -f web
Ausblick: Die Formatauswahl ist so ausgelegt, dass sie später eine automatische Formaterkennung pro Service über einen Onlinedienst unterstützt (noch nicht implementiert).

port-forward (Alias pf) — einen lokalen Port weiterleiten

Bindet einen lokalen TCP-Port und leitet ihn an einen Port innerhalb eines Containers weiter, ohne diesen Port im Cluster zu veröffentlichen. Der lokale Port entspricht standardmäßig dem entfernten. Ein Service als Ziel leitet an genau eine seiner Tasks weiter (die, auf die das Ziel aufgelöst wird), nicht über Replicas hinweg. Standardmäßig an 127.0.0.1 gebunden. Drücke Ctrl-C zum Stoppen.

swarmexec port-forward [flags] <target> [local:]remote
FlagStandardBeschreibung
--address127.0.0.1lokale Adresse zum Binden (Loopback hält den Port aus deinem Netzwerk fern)
--nodeNode-Hinweis/-Override für container-id-Ziele
--connect-timeout10sTimeout für die Verbindung zum Agent
$ swarmexec port-forward db 5432 # localhost:5432 → container:5432 $ swarmexec pf web 9090:8080 # localhost:9090 → container:8080

volume ls — Volumes auflisten

Listet Volumes über alle Nodes hinweg und welche Nodes jedes halten. Swarm-Volumes sind node-lokal, der Client fragt daher jeden Node ab und aggregiert. Optionaler Teilstring-Namensfilter. Spalten: VOLUME  DRIVER  NODES  USED BY  AGE (plus SIZE mit --size).

swarmexec volume ls [name-filter]
FlagStandardBeschreibung
--sizefalsezusätzlich die Größe jedes Volumes auf der Platte berechnen (langsamer: du pro Volume)
--sortnamesortieren nach: name | nodes | used | age | size (size impliziert --size)
--reversefalseSortierrichtung umkehren
--connect-timeout10sConnect-Timeout pro Node
--jsonfalseJSON statt einer Tabelle ausgeben

volume rm — ein Volume entfernen

Entfernt ein Volume auf jedem Node, der es hält (--all), oder auf bestimmten Nodes (--node, wiederholbar). Eines von beiden ist erforderlich.

swarmexec volume rm <name> (--all | --node N …)
FlagStandardBeschreibung
--allfalseauf jedem Node entfernen, der das Volume hält
--nodenur auf diesen Nodes entfernen (wiederholbar)
--forcefalsedocker's Force-Flag durchreichen
-y, --yesfalsenicht zur Bestätigung auffordern
--connect-timeout10sConnect-Timeout pro Node

ui — interaktive TUI

Eine interaktive Ansicht von Containern und Volumes, mit integriertem exec, logs und Port-Forwarding. Braucht ein interaktives Terminal. Siehe Die TUI für die Tasten.

swarmexec ui [service]
FlagStandardBeschreibung
--connect-timeout10sTimeout für die Verbindung zu einem Agent

config show — Konfiguration inspizieren

Gibt die effektive, zusammengeführte Client-Konfiguration mit maskiertem Secret aus.

swarmexec config show [--json]

context (Alias ctx) — Docker-Contexts verwalten

Erstellt und verwaltet die Docker-Contexts, die --context (und $DOCKER_CONTEXT) für die Manager-API auflösen. swarmexec schreibt docker's eigenen On-Disk-Speicher, sodass hier erstellte Contexts mit der docker-CLI austauschbar sind — und du brauchst docker nicht mehr installiert, selbst um einen zu erstellen. Der eingebaute default-Context kann nicht entfernt werden.

context create <name> — erstellt einen Context, der auf einen Manager-Host zeigt (nimmt genau ein Namensargument):

swarmexec context create <name> --docker-host <endpoint>
FlagStandardBeschreibung
--docker-hosterforderlich — Docker-Daemon-Endpoint: ssh:// | tcp:// | unix:// | npipe://
--descriptionoptionale Beschreibung
--ssh-jumpSSH-Jump-Host(s) für einen ssh://-Context, kommagetrennt (mehrstufiges ProxyJump / -J); wird sowohl in die Docker-API- als auch in die Agent-Tunnel-Verbindung eingefügt
--usefalseihn außerdem zum aktuellen Context machen

context ls (Alias list) — Contexts auflisten; Spalten NAME  CURRENT  DOCKER ENDPOINT (der aktive mit * markiert):

swarmexec context ls [--json]

context use <name> — den aktuellen Context setzen:

swarmexec context use <name>

context rm <name> [name...] (Alias remove) — einen oder mehrere Contexts entfernen:

swarmexec context rm <name> [name...]
FlagStandardBeschreibung
-f, --forcefalseerforderlich, um den aktuellen Context zu entfernen (seine Auswahl wird auf default zurückgesetzt)
$ swarmexec context create prod --docker-host ssh://ops@manager --use $ swarmexec context ls $ swarmexec --context prod ps

Gemeinsame ssh-Verbindungen

Bei einem ssh://-Kontext erreicht swarmexec den Cluster gleich zweimal über ssh: die Docker-Manager-API über ssh's Connection-Helper, und jeden Node-Agenten über einen eigenen Tunnel zum selben Host. Jedes Exec, jeder Log-Stream, jedes Port-Forward, jeder Stats-Poll und jeder Refresh-Zyklus öffnete bisher eine frische ssh-Verbindung — ein TCP-Handshake, ein Schlüsselaustausch und eine Authentifizierung pro Dial, und noch einmal pro Jump-Host — gegen einen Bastion-Host, der einen Moment vorher schon verbunden war.

swarmexec lässt sie jetzt einen Transport teilen, mit OpenSSHs eigenem ControlMaster: die erste Verbindung zu einem Ziel öffnet ihn, jede weitere wird ein Kanal darauf. Gemessen an einem Drei-Node-Cluster hinter einem Jump-Host ging swarmexec doctor von 8 Authentifizierungen und 3,6 s auf 2 und 1,0 s. Am Verkehr selbst ändert sich nichts, und der Effekt wächst mit dem, was man in einer Sitzung tut.

10. Die TUI

swarmexec ui hat sieben Tabs — Stacks/Services (1), Volumes (2), Forwards (3), Networks (4), Secrets (5), Nodes (6), Configs (7) — eine Context-Sidebar am rechten Rand und einen zweizeiligen Footer: oben die Tastenhinweise pro Tab, dann eine Statuszeile mit dem aktiven Docker-Context (ctx <name>, sodass immer klar ist, auf welchem Cluster du bist), einer Live-Cluster-Zusammenfassung, der Anzahl der Forwards und — im Volumes-Tab — wie viele Volumes du ausgewählt hast. Diese Tasten funktionieren auf jedem Tab:

Die Tab-Leiste ist responsiv: in einem schmaleren Terminal wechselt sie auf Kurzlabels — St/Sv, Vol, Fwd, Net, Sec, Node, Cfg — damit alle sieben Tabs sichtbar bleiben, statt dass die letzten abgeschnitten werden. (Cfg ist Configs.) Die Zifferntasten und die Maus-Klickflächen bleiben unverändert.

Geändert: die Contexts waren Tab 6 und sind jetzt die Sidebar (c). Nodes und Configs rücken auf 6 und 7 vor.
TasteAktion
?das Tastenbelegungs-Overlay öffnen — die vollständige, lebende Tastenreferenz (sie wird aus deiner Tastenbelegung erzeugt, umbelegte Tasten erscheinen also korrekt); die einzeilige Fußzeile hat nur Platz für die meistgenutzten Tasten
Tabzum nächsten Tab wechseln
18zu Stacks/Services / Volumes / Forwards / Networks / Secrets / Contexts / Nodes / Configs springen
j krunter / hoch (auch )
rden aktiven Tab und die Cluster-Zusammenfassung aktualisieren
ydie aktuelle Liste in die Zwischenablage kopieren (OSC52)
mMaus-Capture umschalten (aus = das eigene Auswählen/Kopieren deines Terminals)
`den Live-Log-Viewer öffnen / schließen (siehe unten)
qbeenden

Stacks/Services-Tab

Tab 1, früher Containers. Er ist ein Baum, Stack → Service → Container, und darin stecken Exec, Logs, Port-Forwarding, Inspect und die Service-Editoren.

:latest-Versionsprüfung. Swarm fixiert :latest zum Deploy-Zeitpunkt auf einen Digest, sodass ein mit :latest getaggter Service tatsächlich ein festes Image ausführt. swarmexec löst das gegen die Registry auf (mit deinen lokalen Docker-Anmeldedaten) und annotiert die Service-Zeile direkt nach der Image-URI: die echte Version in Klammern — aus dem org.opencontainers.image.version-Label des Images gelesen — und einen Aufwärtspfeil ↑, wenn das aktuelle :latest der Registry ein neuerer Digest ist als der, auf den der Service fixiert ist (z. B. nginx:latest (1.4.0) ↑). Best-Effort und gecacht: Registry-Fehler lassen die Zeile einfach ohne Annotation. Der IMAGE-Abschnitt des Inspect-Overlays zeigt dieselbe Version und, wenn ein neueres Image existiert, eine auswählbare newer version available-Zeile — geh darauf und drücke u (oder Enter), um den Service zu aktualisieren.

Version setzen mit u — nicht nur, wenn es ein Update gibt. u hängt nicht mehr davon ab, dass ein Upgrade angeboten wird. Es steht in jedem Service-Inspect zur Verfügung, dessen Image-Tags swarmexec aus der Registry lesen konnte — bei versionsgepinnten und bei :latest-Services gleichermaßen — und aus jeder Zeile des Overlays, sodass du den Hinweis nie suchen musst. Dieselbe Version erneut setzen, das aktuell Laufende pinnen oder auf einen älteren Tag zurückgehen sind ganz normale Anwendungsfälle; du musst nicht warten, bis es ein Update gibt. Der Text in der Fußzeile zeigt dir, in welcher Situation du bist: u update version, wenn eine neuere Version gefunden wurde (dann gibt es auch die newer version available-Zeile mit einem konkreten Ziel), und u set version, wenn nicht.

Was u öffnet, hängt davon ab, was die Registry weiß. Bei einem versionsgepinnten Service mit einer neueren Version ist es die Versionsauswahl: ein Eingabefeld, dessen Autovervollständigung die neueren Tags derselben Familie vorschlägt, höchste zuerst und mit der höchsten vorbelegt. Läuft der Service bereits auf dem neuesten Tag, ist es dieselbe Auswahl, nur fallen die Vorschläge auf alle Tags des Repos zurück (vorbelegt mit dem laufenden Tag) — so pinnst du oder rollst zurück. Ein Service auf :latest bekommt die Auswahl ebenfalls, mit allen bekannten Tags: eine konkrete Version dort zu wählen ist der Weg, einen :latest-Service vom gleitenden Tag weg zu pinnen — genau das, was der Risiko-Befund unpinned-image (siehe das Sicherheits-Overlay weiter unten) verlangt. Die einzige Ausnahme ist ein :latest-Service mit anstehendem Digest-Update: Der bleibt bei der einzelnen Bestätigung auf den neuen Digest der Registry, die er immer war — eine Auswahl würde dort dazu verleiten, latest einzutippen, was auf das nackte Tag auflöst und damit still genau die Digest-Pinnung wegwerfen würde, die das Update auffrischen soll.

In der Auswahl ist die Vorschlagsliste auf 25 Einträge begrenzt (ein viel genutztes Repo listet leicht Hunderte), und das Feld bleibt frei editierbar: du kannst jeden vorhandenen Tag eintippen, auch einen älteren, um ihn zu pinnen oder zurückzurollen auf ein bewährtes Release. Ein eingetippter Tag wird gegen die Repo-Tags geprüft (ein nicht gelisteter Tag wird abgelehnt), und die Auswahl eines älteren Tags zeigt vor dem Anwenden eine Downgrade-Warnung. In jedem Fall ist die Änderung ein ServiceUpdate / Rolling Update, hinter einer Bestätigung. Liefert die Registry überhaupt keine Tags und gibt es auch kein konkretes Ziel — eine private oder nicht erreichbare Registry oder ein per Digest gepinnter Service —, bekommst du einen erklärenden Hinweis, und es wird nichts geändert.

TasteAktion
/die Suchleiste öffnen (filtert nach Service / Container / Node)
h lein-/ausklappen über alle drei Ebenen. h klappt die Zeile unter dem Cursor zu — ein Stack die ganze Gruppe, ein ausgeklappter Service seine Container — und wo nichts mehr zuzuklappen ist, springt es stattdessen heraus zum Elternknoten, sodass wiederholtes Drücken nach oben läuft: Container → Service → Stack. l klappt die Zeile unter dem Cursor auf oder steigt in ihr erstes Kind ab, wenn sie schon offen ist
Enterauf einem Container: das Aktionsmenü öffnen; auf einem Service oder einem Stack: ihn ein-/ausklappen
sStack-Gruppierung umschalten — gruppierter Baum ⟷ flache Service-Liste (siehe unten)
LLogs (großes L) — auf einem Container dessen eigene Logs, auf einem Service die aggregierten Logs aller seiner Tasks
!das Sicherheitsrisiken-Overlay für die gesamte Service-Liste öffnen (siehe unten)
pPort-Forward für die Task unter dem Cursor
iden Node unter dem Cursor inspizieren — ein navigierbares Overlay mit tabellarischer Zusammenfassung; bewege die Auswahl mit / oder j/k, y/Enter kopiert die ausgewählte Zeile, eine Tab-Leiste oben benennt die drei Ansichten, 1/2/3 wählen Tabelle / Stats / rohes Daemon-JSON direkt aus und t schaltet reihum durch (Esc/q/i schließt). Auf einem Service bearbeitet das Overlay ihn auch: s Skalieren, f Force-Update, p Ports, l Labels, e Env, n Netzwerke, S Secrets, v Mounts, A Aliase; D diagnostiziert, warum er nicht überall läuft; X entfernt ihn
Xeinen Service direkt aus dem Baum entfernen (großes X, also Shift+x) — ohne Umweg über das Inspect-Overlay. Worauf es wirkt, richtet sich nach der Zeile unter dem Cursor: auf einer Service-Zeile wird dieser Service entfernt; auf einer Container-Zeile der zugehörige Service des Containers, denn eine einzelne Task lässt sich nicht für sich entfernen — Swarm würde sie sofort neu einplanen; auf einer Stack-Zeile wird nichts entfernt, stattdessen weist eine kurze Notiz in der Fußzeile darauf hin, dass die Services eines Stacks einzeln entfernt werden müssen oder mit docker stack rm <stack>. Es läuft über dieselbe Bestätigung wie das X im Inspect weiter unten — es löscht den Service dauerhaft und stoppt alle seine Tasks; das kann nicht rückgängig gemacht werden. Eine feste Großbuchstaben-Taste, keine umbelegbare Keymap-Aktion; die Fußzeile führt es rot als X remove, das ?-Overlay listet es ebenfalls. Das X im Inspect-Overlay bleibt unverändert

Jeder Service wird angezeigt (auch einer, der auf null skaliert ist), jede Zeile gerendert wie eine docker service ls-Zeile — Name, Modus, laufende/gewünschte Anzahl, Image und veröffentlichte Ports — und danach eingefärbt, wie es dem Service tatsächlich geht: Die laufenden/gewünschten Tasks geben die Grundfarbe vor (aqua = alle Tasks oben, orange = teilweise, rot = unten, grau = auf null skaliert), ein fehlschlagender Healthcheck überschreibt sie (siehe unten). Ein /-Marker zeigt, ob ein Service eingeklappt oder ausgeklappt ist; seine laufenden Container verschachteln sich darunter. Ein Service mitten in einem Rolling Update trägt ein farbiges Badge in seiner Baumzeile — ⟳ updating oder ↺ rolling back — und derselbe Status erscheint im Service-Inspect-Overlay; es verschwindet, sobald das Update abgeschlossen ist.

Eingefärbt nach Healthcheck, nicht nur nach Replica-Zahl. Die Farbe einer Service-Zeile kam bisher allein aus laufend gegen gewünscht — ein Service, dessen sämtliche Container ihren Healthcheck rissen, stand also weiterhin als ruhiges aquafarbenes 3/3 da. Die Zahl stimmte, die Zeile log. Der Task-Status von Swarm hilft hier nicht weiter: Eine Task ist running, während ihr Container jede Probe verfehlt — das Urteil muss also vom Node kommen. Eine fehlschlagende Probe gilt jetzt als Verschlechterung derselben Art wie ein fehlendes Replica:

Die Marker. Eine Service-Zeile trägt ✖ N unhealthy in Rot oder ◌ N starting in Gelb, solange die Proben noch nicht durch sind; ein Container-Blatt trägt ✖ unhealthy bzw. ◌ starting. Unhealthy sticht starting — das ist das, wo man ran muss. Eine Stack-Zeile fasst denselben Marker über alles darunter zusammen (siehe Nach Stack gruppiert weiter unten). Ein gesunder Container bekommt keinen Marker, einer ohne Healthcheck ebenso wenig; so bleibt der Marker eine Aussage. Sie stehen neben den cpu/mem-Ressourcenmarkern weiter unten, und die Gesundheit kommt in der Zeile zuerst: Sie ist das, was der Zahl direkt daneben widerspricht.

Woher das Urteil kommt — und was es kostet. Nichts zusätzlich: Es fährt bei den Ressourcen-Messwerten weiter unten mit — derselbe Agent, derselbe Aufruf, keine zusätzliche API-Anfrage. Die Container-Liste, die der Agent bei jedem Sampling-Durchlauf ohnehin holt, trägt das Urteil in ihrer Statuszeile (Up 3 days (healthy)) — genau die Zeichenkette, die auch docker ps ausgibt. Der Parser ist bewusst streng: Was er nicht kennt, wird zu kein Urteil statt zu einer Vermutung. Formuliert der Daemon diese Zeile eines Tages um, hört swarmexec also auf, Bescheid zu wissen, statt einen fehlschlagenden Container als gesund zu melden. Es gilt dieselbe Voraussetzung wie für die Nutzungszahlen: Es braucht Agents aus diesem Release oder neuer, ein noch nicht neu ausgerolltes Cluster zeigt also gar keine Health-Marker — ausrollen mit swarmexec init --force.

Marker für die tatsächliche CPU- und Memory-Nutzung. Neben diesen Badges kann eine Zeile auch zeigen, was sie gerade wirklich verbraucht — bisher konnte swarmexec nur anzeigen, was der Scheduler gebucht hat (siehe den Nodes-Tab weiter unten). Überschreitet ein Container oder ein Service 70 %, bekommt er einen orangen Marker, ab 90 % einen roten — am Ende der Zeile, mit der Ressource in Worten (cpu für CPU, mem für Memory) und dem Prozentwert (cpu 94 %, mem 91 %). Sind beide heiß, erscheinen beide. Worte statt Symbolen, bei gleicher Breite: Ein Marker, dessen Bedeutung man erst nachschlagen muss, tut seinen Job nicht — und Buchstaben können in keinem Terminal an der Darstellung scheitern. Unter 70 % wird nichts gezeichnet, damit die Marker ein Signal bleiben und keine Tapete. Eine Service-Zeile nimmt den schlechtesten ihrer Replicas, nicht den Durchschnitt — ein Durchschnitt verdeckt genau den einen Container, der gleich stirbt, und der ist der interessante. (Der absolute Memory-Wert einer Service-Zeile ist die Summe über die Replicas, der Prozentwert das Maximum.)

Wovon der Prozentwert ein Prozentwert ist. Gemessen wird gegen das, was der Container tatsächlich nutzen darf: sein eigenes Limit, wenn er eins hat, sonst die Kapazität des Nodes. Genau darauf kommt es an — 91 % eines 256-MB-Limits heißt, ein OOM-Kill steht bevor; 91 % eines 64-GB-Nodes ist eine ganz andere Geschichte. Die Stats-Ansicht des Inspect-Overlays zeigt pro Container beide Seiten dieser Division und benennt die Bezugsgröße unter der Tabelle.

Woher die Zahlen kommen — und warum sie verzögert eintreffen. Die Auslastung steht nicht in der Manager-API: Container-Stats sind node-lokal. Sie kommen aus einem node-lokalen Agent-RPC (Stats); jeder Agent sampelt seine eigenen Container im Hintergrund und antwortet aus dem Speicher, der Client fragt ihn also einfach im ohnehin vorhandenen Refresh-Zyklus ab. Daraus folgen zwei Dinge. Ein CPU-Prozentwert ist eine Differenz zwischen zwei Messungen und existiert daher erst, wenn der Agent zwei genommen hat (ein paar Sekunden) — solange zeigen die Ansichten , nie 0 %; Memory braucht keine Differenz und ist ab der ersten Messung da. Und ein Agent sampelt nur, solange auch jemand hinschaut: Rund eine Minute nach der letzten Anfrage hört er auf und vergisst seine Messwerte, statt veraltete auszuliefern. Deshalb brauchen die ersten Zahlen nach dem Öffnen der UI einen Moment. Das ist Absicht — ein Agent, dem niemand zusieht, soll nichts kosten.
Nirgendwo Marker? Prüfe die Agent-Version. Die Auslastung ist eine Zugabe und fällt still aus: Ein Node, dessen Agent nicht erreichbar ist — oder älter als dieses Release und damit ohne Kenntnis des Stats-RPC —, liefert schlicht keine Messwerte. Die Marker bleiben aus, der Node-Block fehlt, alles andere bleibt unberührt; der Client nimmt so einen Node zudem für eine Weile aus dem Rennen, statt ihn bei jedem Refresh neu anzufragen. Auf einem Cluster, das noch nicht neu ausgerollt wurde, gibt es deshalb überhaupt keine Nutzungszahlen und keine Health-Marker, bis die Agents aktualisiert sind — ausrollen mit swarmexec init --force. Das ist mit Abstand der häufigste Grund, nichts zu sehen.

Nach Stack gruppiert. docker stack deploy versieht jeden Service, den es anlegt, mit dem Label com.docker.stack.namespace. swarmexec liest dieses Label aus der Service-Spec, die der Manager ohnehin schon geliefert hat — Swarm kennt kein Stack-Objekt, das Label ist die einzige Verbindung — und verschachtelt den Baum drei Ebenen tief: Stack → Service → Container. Eine Stack-Zeile zeigt den Stack-Namen, wie viele Services darunter hängen und die summierte laufende/gewünschte Task-Anzahl ((3 svc · 7/8)), eingefärbt nach genau derselben Regel wie eine Service-Zeile — der summierten laufend/gewünscht-Zahl, mit derselben Healthcheck-Übersteuerung darüber. Dahinter stehen die Sammelzähler, sobald sie nicht null sind: ⟳ n — Services in diesem Stack mitten in einem Rolling Update🛡 n — Services mit einem handlungswürdigen Sicherheitsbefund, dasselbe Schild, das die Service-Zeilen tragen — und danach der Health-Marker, ✖ N unhealthy oder ◌ N starting, summiert über jeden Container jedes Service im Stack. Auch ein zugeklappter Stack verrät so, ob darin etwas Aufmerksamkeit braucht. Gerade beim Health-Marker zählt das: Die Stack-Zeile ist die Ebene, die man zuerst überfliegt — ein trügerisches „läuft alles“ ist hier schlimmer als eine Ebene tiefer, nicht harmloser.

Services, die mit docker service create angelegt wurden, tragen kein Stack-Label; sie landen gesammelt unter (no stack), das immer zuletzt einsortiert wird und damit nie echte Stacks nach unten drängt. Nichts wird versteckt — jeder Service erscheint weiterhin genau einmal. Stacks werden nach Namen sortiert und starten ausgeklappt, sodass der gruppierte Baum dieselben Services zeigt wie der flache; von dir gesetzte Falten überleben die Auto-Aktualisierung.

Die Gruppierung ist standardmäßig an, greift aber erst, wenn mindestens ein Service im Cluster tatsächlich ein Stack-Label trägt — auf einem Cluster ohne Stacks sieht der Baum genau aus wie bisher, ohne sinnlosen (no stack)-Elternknoten. s schaltet zwischen gruppiertem Baum und flacher Service-Liste um (die Fußzeile führt es als s stacks); gibt es nirgends ein Stack-Label, sagt es das, statt einen identischen Baum neu zu zeichnen. Die Taste ist als stack_group in keys.yaml umbelegbar.

Die Suche (/) bleibt unverändert: der Filter greift weiterhin auf Service, Container und Node. Ein Stack, den der Filter leert, verschwindet aus dem Baum, und die Zähler jeder Stack-Zeile beschreiben, was tatsächlich darunter gelandet ist — sie folgen also dem Filter, statt weggefilterte Services anzupreisen.

Das Aktionsmenü bietet Logs, Bash, Sh, Shell as user… (fragt nach einem Benutzer/einer UID, wie docker exec -u — für Images, deren Standardbenutzer die benötigten Werkzeuge oder Rechte nicht hat) und Port forward (nicht verfügbare Shells werden nach einem Test ausgegraut). In der Suchleiste behält Enter den Filter und kehrt zur Liste zurück; Esc löscht den Filter und schließt die Leiste.

Sicherheitsrisiken (!). Bei jedem Abruf der Service-Liste lässt swarmexec eine Reihe kleiner statischer Analyzer über die Spec jedes Service laufen — über dieselbe Spec, die der Manager ohnehin schon geliefert hat. Es gibt also keinen zusätzlichen API-Aufruf, der Agent ist nicht beteiligt, und in deinen Containern wird nichts ausgeführt. Ein Service mit einem Befund, der Handlung verdient, wird im Service-Baum mit einem vorangestellten 🛡 markiert — in einem Slot fester Breite vor dem Namen, sodass die Schilde eine senkrechte Scan-Spalte bilden. ! öffnet das Sicherheitsrisiken-Overlay, und w darin schreibt einen clusterweiten Markdown-Bericht — der mehr abdeckt als das Overlay, weil er die Prüfungen ergänzt, die dem Cluster gehören und keinem Service.

Die acht Prüfungen, die es heute gibt. Zuerst die, die einen Service markieren können, am Ende die rein informativen:

PrüfungSchweregradWas sie meldet
docker-socket — „Docker socket mounted in“highder schwerwiegendste Befund hier. Ein Bind-Mount, dessen Quelle der Socket des Docker-Daemons ist — /var/run/docker.sock, /run/docker.sock oder jeder Quellpfad, der auf /docker.sock endet. Alles in diesem Container kann mit dem Socket sprechen, und wer mit dem Socket sprechen kann, kann auf diesem Node einen privilegierten Container starten — das ist faktisch root auf dem Host, ganz gleich, als wer der Container selbst läuft. Read-only entschärft das nicht: der Socket ist eine API, keine Datei, deren Inhalt zählt — der Befund sagt das bei einem Read-only-Mount auch selbst dazu. Genannt wird der Zielpfad, an dem der Socket eingehängt ist
added-capability — „capability NAME added“high / mediumeine Linux-Capability, die der Service seinem Container zusätzlich gibt. Swarm kennt kein --privileged, Capabilities sind also der Weg, über den ein Service zusätzliche Macht auf dem Host anfordert — deshalb lohnt der Blick auf die hinzugefügte Menge. Acht gelten als high, jeweils mit der Begründung, die der Befund mitführt: ALL (vergibt jede Capability), SYS_ADMIN (fast root: Mount-, Namespace- und cgroup-Kontrolle), SYS_MODULE (kann Kernel-Module laden), SYS_PTRACE (kann andere Prozesse einsehen und steuern), SYS_RAWIO (Raw-I/O-Zugriff auf Geräte), DAC_READ_SEARCH (umgeht Leserechte-Prüfungen im Dateisystem), NET_ADMIN (volle Kontrolle über das Netzwerk des Nodes), NET_RAW (kann Rohpakete fälschen und mitschneiden). Jede andere hinzugefügte Capability ist medium — ein Privileg über die Standardmenge hinaus. Ein Befund pro hinzugefügter Capability; beide Schreibweisen werden erkannt, in beliebiger Groß-/Kleinschreibung (CAP_SYS_ADMIN und SYS_ADMIN)
host-network — „runs on the host network“highder Service hängt am Netzwerk namens host: der Container teilt sich den Netzwerk-Stack des Nodes, es gibt also keine Netzwerk-Isolation, und das Published-Port-Mapping von Swarm greift nicht mehr. Er erreicht alles, was der Node erreicht — einschließlich Diensten, die nur auf localhost lauschen und die man üblicherweise für aus einem Container heraus unerreichbar hält
unconfined — „seccomp disabled“ / „AppArmor disabled“highdie Sandbox auf Kernel-Ebene ist für den Container abgeschaltet: seccomp steht auf unconfined — der Container darf jeden Syscall absetzen, womit die wichtigste Hürde gegen Kernel-Exploits wegfällt — oder die AppArmor-Einschränkung ist deaktiviert. Beides wird getrennt gemeldet; ein Service, der beides abschaltet, bekommt zwei Befunde
root-user — „runs as root“highdie Spec legt den Container ausdrücklich auf root fest — User=root oder UID 0 (auch numerische Formen wie 00 oder +0 werden erkannt; betrachtet wird nur der Benutzerteil von user:group)
secret-in-env — „secret in environment variable“highein nach einem Credential aussehender Env-Schlüssel enthält einen literalen Wert in der Service-Spec, lesbar für jeden, der die Spec lesen darf. Nutze stattdessen ein Docker-Secret oder die *_FILE-Konvention
root-user — „no user set“lowkein Benutzer ist gesetzt, der Container läuft also als der Standardbenutzer des Images — oft root. Nur informativ, markiert den Service also nicht: ein nicht gesetzter Benutzer ist bei fast jedem Service der Swarm-Standard, und ob es wirklich root ist, hängt vom USER des Images ab, das die Manager-Spec nicht preisgibt
no-resource-limits — „no resource limits“lowder Task setzt weder ein CPU- noch ein Memory-Limit; ein außer Kontrolle geratener Container kann damit den ganzen Node aufbrauchen und alles andere darauf aushungern. Nur informativ — siehe unten
unpinned-image — „image not pinned“lowdas Image ist auf :latest festgelegt oder trägt überhaupt kein Tag (was auf :latest hinausläuft) — die laufende Version kann sich ohne Spec-Änderung ändern, ein Deploy ist damit nicht reproduzierbar. Ein per Digest gepinntes Image (…@sha256:…) ist exakt reproduzierbar und wird nie gemeldet, und ein Doppelpunkt, der zum Port des Registry-Hosts gehört (registry:5000/img), wird nicht für ein Tag gehalten. Nur informativ — siehe unten

Warum die informativen Prüfungen nie einen Service markieren. Erst ein Befund oberhalb von low — high oder medium — macht einen Service handlungsrelevant und setzt das 🛡 an seine Baumzeile. Die Low-Befunde (no-resource-limits, unpinned-image und „no user set“ aus root-user) treffen auf nahezu jeden Service in einem echten Cluster zu: ein Badge dafür würde fast jede Zeile mit einem Schild versehen und genau das Signal auf einen Blick zerstören, für das der Marker da ist. Unter den Tisch fallen sie trotzdem nicht — sobald ein Service wegen etwas anderem markiert ist, stehen seine Low-Befunde im Overlay bei den übrigen, also dort, wo sie sich wirklich zu lesen lohnen.

Es wird nie ein Secret-Wert angezeigt. Von den acht Prüfungen schaut nur secret-in-env überhaupt auf Umgebungsvariablen, und ihr Befund nennt ausschließlich den Schlüssel der Env-Variable; der Wert wird nie in den Befund eingelesen, nie gerendert und nie kopiert. Die Prüfung ist außerdem auf Ruhe ausgelegt: sie vergleicht ganze, durch _ getrennte Tokens (PASSWORD, PASSWD, PASS, PASSPHRASE, SECRET, TOKEN, APIKEY, CREDENTIAL(S), PRIVATEKEY sowie die Paare API_KEY, ACCESS_KEY, PRIVATE_KEY, SECRET_KEY, CLIENT_SECRET, AUTH_TOKEN), sodass COMPASS, PASSENGER_PORT oder BYPASS_AUTH nicht gemeldet werden. Schlüssel, die ein Credential nur referenzieren, sind ausgenommen (_FILE, _PATH, _URL, _URI, _NAME, _ID, _TYPE, _ENABLED, _REQUIRED, _LENGTH, _TIMEOUT, _TTL, _EXPIRY, _ALGORITHM) — ebenso Werte, die offensichtlich kein Credential sind: ein absoluter Pfad, ein Boolean, eine Zahl.

Das Overlay listet jeden markierten Service („N of M service(s) flagged · 8 checks“), nach Service gruppiert, jeden Befund in einer eigenen Zeile mit einem Schweregrad-Punkt — ● high, ● medium, ● low — einem kurzen Titel und einer einzeiligen Erklärung. Befunde stehen nach Schweregrad absteigend. Sobald ein Service wegen etwas Handlungsrelevantem markiert ist, werden auch seine rein informativen Low-Befunde aufgeführt. Ist der Service unter dem Cursor darunter, wird er vorab hervorgehoben und ins Bild gescrollt. Ist nichts markiert, sagt das Overlay das ausdrücklich und nennt jede Prüfung, die gelaufen ist („Checked: Docker socket mounted in, added Linux capabilities, host network, seccomp / AppArmor disabled, container user, secrets in environment variables, missing resource limits, unpinned image.“) — so sagt die Entwarnung auch, was sie abdeckt. Ist die Service-Liste noch nicht geladen, meldet es es wurde noch nichts gescannt, statt fälschlich Entwarnung zu geben. j/k scrollen, Esc (oder q, oder erneut !) schließt.

Der Scan ist eine erweiterbare Registry von Analyzern, keine feste Liste: eine neue Prüfung erscheint automatisch sowohl im Baum-Marker als auch im Overlay. Auch der Text des Overlays kommt aus dieser Registry — die Zahl der Prüfungen in seiner Kopfzeile und die „Checked:“-Liste werden aus den Analyzern selbst erzeugt und können deshalb nicht auseinanderlaufen mit dem, was tatsächlich gelaufen ist, wie es eine fest verdrahtete Liste täte. Die Taste ist als security_risks in keys.yaml umbelegbar.

i öffnet ein Inspect-Overlay für den Node unter dem Cursor — auf einem Service zeigt es docker service inspect, und auf einem Container-Blatt das Swarm-Task-Inspect (die Sicht des Managers auf diese Instanz: State, Slot, Node, Container-ID, Container-Spec, Ressourcen und Statushistorie). Beide kommen vom Swarm-Manager. Das Overlay öffnet sich mit einer tabellarischen, operator-first Zusammenfassung mit Abschnitten, die nach operativer Relevanz geordnet sind — zuerst Netzwerke, Labels, Volumes/Mounts und Secrets (und Configs), dann Ports, Image, Modus, Env, Ressourcen, Placement und Update-Policy (plus State, Node und Container-ID bei einem Container/einer Task), mit IDs und Zeitstempeln zuletzt. Die erste Zeile des Overlays — innerhalb des Rahmens und fest über dem scrollenden Inhalt — ist eine Tab-Leiste, die seine drei Ansichten benennt: table 1   stats 2   raw json 3, die aktuelle in der Akzentfarbe. Sie hat dieselbe Form wie die Tab-Leiste des Hauptfensters — ein Label, gefolgt von der Ziffer, die es auswählt. 1, 2 und 3 springen direkt zu Tabelle, Stats und rohem Daemon-JSON, und t schaltet weiterhin reihum Tabelle → Stats → rohes JSON und wieder zurück. Der Footer-Hinweis lautet nur noch 1-3/t view, und der Rahmentitel wiederholt die aktuelle Ansicht nicht mehr — er heißt schlicht inspect service foo, denn die Leiste sagt bereits, wo du bist. Beachte, dass die tabellarische Ansicht die Task-Sicht des Managers ist, nicht ein vollständiges node-lokales docker container inspect des laufenden Containers — ein echtes Container-Inspect über den Agent ist geplant.

Die Stats-Ansicht. Die mittlere der drei zeigt die tatsächliche Ressourcennutzung — dieselben Messwerte, die auch den Baum markieren; das Öffnen kostet also keinen zusätzlichen Aufruf, und die Zahlen aktualisieren sich weiter, solange die Ansicht offen ist. Sie ist eine Tabelle, eine Zeile pro Container des Service (oder der eine Container eines Task-Inspects):

RESOURCE USAGE measured on each node, refreshed with the tree CONTAINER NODE HEALTH CPU MEMORY gl_gitlab.1 docker3v2 healthy 0.07 / 4.00 cores (2%) 6.823GiB / 8GiB (85%) limits are the container's own where it has one, otherwise the node's capacity

Eine Tabelle, weil ein nackter Prozentwert nicht lesbar ist, wenn man nicht ohnehin weiß, wie das Werkzeug misst — 94 % wovon? Jede Zelle bringt ihre eigenen Einheiten mit, 0.07 / 4.00 cores braucht also keine Legende, und der Prozentwert steht direkt neben der Zahl, von der er ein Prozentwert ist. Mehrere Replicas vergleicht man, indem man eine Spalte entlangliest, statt gestapelte Blöcke gegeneinanderzuhalten. Die Spalte HEALTH formuliert das Urteil in Worten — healthy, unhealthy, starting oder none für einen Container, der gar keinen Healthcheck deklariert. none und healthy sind mit Absicht zwei verschiedene Worte und nicht ein Wort und eine Lücke: „kein Healthcheck konfiguriert“ und „die Probe läuft durch“ sind verschiedene Tatsachen, und eine Lücke läse sich wie das Zweite. Ein Container, den kein Agent gemeldet hat — nicht erreichbarer Node, ein für den Stats-RPC zu alter Agent oder ein erst seit der letzten Messung laufender Container —, zeigt in beiden Ressourcenspalten no reading statt einer Null, die sich wie „im Leerlauf“ läse.

Die Fußnote unter der Tabelle benennt den Nenner: das eigene Limit des Containers, wenn er eins hat, sonst die Kapazität des Nodes. Genau das macht einen Prozentwert erst aussagekräftig — 85 % eines 8-GiB-Limits und 85 % eines 64-GiB-Nodes sind zwei verschiedene Geschichten. Memory steht in binären Einheiten (MiB/GiB), denselben, die auch das Node-Detail verwendet und die docker selbst meldet; dieselbe Zahl liest sich also nie an zwei Stellen unterschiedlich. Bei mehr als einem gemessenen Container kommt unter der Tabelle eine Zusammenfassung dazu: CPU und Memory des schlechtesten Replicas — nicht der Durchschnitt, der genau den einen Container verdeckt, der gleich OOM-gekillt wird — plus das gesamte Memory über alle. Der t-Eintrag im ?-Hilfe-Overlay lautet entsprechend cycle table / stats / raw JSON.

Das Overlay ist navigierbar und auswählbar, nicht nur ein einfaches Scrollen: Datenzeilen sind einzeln auswählbar und du bewegst den Cursor Zeile für Zeile mit / oder j/k (Abschnittsüberschriften und Leerzeilen werden übersprungen). y kopiert die ausgewählte Zeile immer über OSC52 in deine Zwischenablage — derselbe Yank-Mechanismus, der auch anderswo verwendet wird — sodass du eine einzelne ID, einen Mount oder eine Adresse greifen kannst, ohne Text von Hand zu markieren. Enter kopiert ebenfalls, außer auf einer einklappbaren NETWORKS-Zeile, wo es stattdessen diese Zeile aus-/einklappt (siehe unten). Alle drei Ansichten (die tabellarische Zusammenfassung, die Stats-Ansicht und das rohe JSON, erreichbar über 13 oder reihum mit t) sind Zeile für Zeile auswählbar und kopierbar. Ein Footer listet immer die verfügbaren Tasten, sodass auf einen Blick sichtbar ist, was du tun kannst; bei einem Service zeigt der Footer auch die untenstehenden Bearbeitungstasten.

TasteAktion
j kdie Auswahl zwischen Datenzeilen bewegen (Überschriften/Leerzeilen übersprungen)
ydie ausgewählte Zeile in die Zwischenablage kopieren (OSC52) — immer
Enterauf einer einklappbaren NETWORKS-Zeile diese Ebene aus-/einklappen — die Zeilen sind zwei Ebenen tief, ein Netzwerk und sein N containers-Drilldown; auf jeder anderen Zeile sie kopieren (OSC52)
1 2 3eine Ansicht direkt auswählen — 1 tabellarische Zusammenfassung, 2 Stats (tatsächliche CPU-/Memory-Nutzung, siehe unten), 3 rohes Daemon-JSON; es sind die Ziffern, die die Tab-Leiste zeigt
treihum durch die drei Ansichten schalten — tabellarische Zusammenfassung → Stats (tatsächliche CPU-/Memory-Nutzung, siehe unten) → rohes Daemon-JSON → zurück; alle drei auswählbar, und die Tab-Leiste oben markiert die aktuelle
aAktionsmenü — jeder Service-Editor und jede Aktion in einer Liste, ohne Shift (nur Service; siehe unten)
s f p l e n S v Aden Service bearbeiten — Skalieren / Force-Update / Ports / Labels / Env / Netzwerke / Secrets / Mounts / Aliase (nur Service; S ist groß, Shift+s, um es von s Skalieren zu unterscheiden; A ist ebenfalls groß; siehe unten)
Esc q idas Overlay schließen

Der NETWORKS-Abschnitt ist einklappbar und beantwortet schon eingeklappt die Frage „unter welcher Adresse ist das erreichbar“: jedes angeschlossene Netzwerk erscheint als eingeklappte Zeile + <network> 🔒 vip 10.0.5.2/24 (2 dns names). Ein 🔒-Schlosssymbol nach dem Netzwerknamen markiert ein verschlüsseltes Overlay-Netzwerk (Data-Plane-Verschlüsselung, --opt encrypted); sein Fehlen bedeutet unverschlüsselt. Bei einem Service-Inspect heißt die Adresse vip — die virtuelle IP des Service in diesem Netzwerk, die Adresse, auf der der Swarm-Load-Balancer antwortet, gelesen aus dem ServiceInspect des Managers (Endpoint.VirtualIPs). Bei einem Container/Task-Inspect heißt sie addr: die eigene Adresse dieses Containers dort. Ein Service im Endpoint-Modus dnsrr hat überhaupt keine VIP — das ist so gewollt und kein fehlender Wert —, deshalb steht statt einer Lücke im ausgeklappten Netzwerk die Zeile no vip — dnsrr endpoint mode, the DNS name resolves to the containers. Das ingress-Netzwerk steht ebenfalls hier, obwohl es in keiner Spec vorkommt — Swarm hängt den Service von sich aus daran (siehe unten). Es wird hinter den Netzwerken aus der Spec angehängt, nie zwischen sie gemischt, und seine Kopfzeile lautet + ingress vip 10.0.0.250/24 (routing mesh): der Hinweis (routing mesh) steht dort, wo eine DNS-Namen-Zählung nur (0 dns names) ergeben könnte.

Geh auf eine Netzwerkzeile und drücke Enter, um sie aus- oder einzuklappen. Ausgeklappt listet sie die DNS-Namen auf, die auf den Service/Container in diesem Netzwerk auflösen — den Service-Namen, tasks.<service> und alle benutzerdefinierten Aliase —, sodass du sehen kannst, unter welchem DNS-Namen er erreicht wird und welche Aliase er pro Netzwerk trägt. Darunter steht eine zweite einklappbare Zeile, + 2 containers (im Singular + 1 container); Enter darauf öffnet die tiefste Ebene: eine spaltenbündige Zeile pro Container, web.1 10.0.1.5/24 host-a — Task-Name, seine Adresse in diesem Netzwerk und der Node, auf dem er läuft (replizierte Tasks heißen <service>.<slot>, globale <service>.<node>, da sie keinen Slot haben).

Gefiltert wird dabei über den Soll-Zustand (desired state) der Task, nicht über ihren aktuellen: gelistet wird jede Task, die der Manager weiterhin laufen lassen will — auch solche, die erst preparing, assigned oder starting sind. Sie halten ihre Adresse bereits, und während eines Rolling Updates sind das die meisten; ein Filter auf den aktuellen Zustand würde den Drilldown also genau dann leeren, wenn er am interessantesten ist. Eine Task, die der Manager aufgegeben hat — Soll-Zustand shutdown, also durch ein Rolling Update ersetzt, weggeskaliert oder fehlgeschlagen —, erscheint nicht: ihre Adresse ist wieder freigegeben und kann längst einem anderen Container gehören, sie zu zeigen wäre also aktiv falsch. Eine Task, die noch keinen Traffic bedient, trägt ihren Zustand in Klammern am Zeilenende, web.1 10.0.1.5/24 host-a (starting); eine laufende hat kein Suffix. Die Zeilen stammen aus einem TaskList der Manager-API, gefiltert auf den Service, und sind Best-Effort — ein API-Fehler lässt den Drilldown einfach leer. Die beiden Ebenen klappen unabhängig voneinander; klappst du das Netzwerk ein, verschwindet die Container-Ebene mit.

Die ingress-Zeile ist diejenige, die in keiner Spec steht: Swarm hängt einen Service von sich aus an das ingress-Netzwerk, sobald er einen Port im Modus ingress veröffentlicht. Diese Anbindung taucht in der Service-Spec nirgends auf — ihre vip ist aber genau die Adresse, auf der das Routing Mesh tatsächlich antwortet, und das ist das Erste, was du wissen willst, wenn ein veröffentlichter Port sich merkwürdig verhält. Deshalb bekommt sie eine eigene Zeile, angehängt hinter den Netzwerken aus der Spec und leicht anders geformt: auf ingress löst kein Service-DNS-Name auf, deshalb trägt diese Kopfzeile dort, wo andere Netzwerke ihre Namen zählen, den Hinweis (routing mesh). Ausgeklappt listet sie stattdessen die über ingress veröffentlichten Ports auf, die den Service überhaupt dorthin gebracht haben — der Grund für die Zeile —, je eine Zeile pro Port: published 2222 -> 22/tcp. Ports, die im Modus host veröffentlicht sind, umgehen das Routing Mesh und werden dort bewusst nicht aufgeführt. Darunter geht es genauso in die Tiefe wie bei jeder anderen Netzwerkzeile: dieselbe Ebene + 1 container, mit der eigenen Adresse jedes Containers im ingress-Netzwerk. Ein Service, der keinen Port über ingress veröffentlicht, hat dort keine VIP — und damit auch keine solche Zeile.

Ein GitLab-Service, der SSH auf 2222 veröffentlicht, beide Ebenen ausgeklappt:

- dbnet vip 10.0.1.137/24 (2 dns names) gl_gitlab tasks.gl_gitlab - 1 container gl_gitlab.1 10.0.1.54/24 docker3v2 - ingress vip 10.0.0.250/24 (routing mesh) published 2222 -> 22/tcp - 1 container gl_gitlab.1 10.0.0.64/24 docker3v2

Kopieren innerhalb von NETWORKS liefert die Adresse ohne Maske10.0.5.2, die Form, die du in ein curl oder ping einfügst: auf einer Netzwerkzeile deren vip/addr, auf einer Container-Zeile die Adresse dieses Containers. Eine Netzwerkzeile ohne Adresse (dnsrr) kopiert wie bisher den Netzwerknamen. Auf beiden Arten einklappbarer Zeilen schaltet Enter um; auf jeder anderen Zeile kopiert es (und y kopiert immer).

Wenn du einen Service inspizierst (nicht ein Container/Task-Blatt, dessen Overlay schreibgeschützt bleibt), stellt der Overlay-Footer auch mehrere Bearbeitungstasten bereit, jede ein Manager-API-ServiceUpdate, das ein Rolling Update / eine Rekonziliation der Tasks des Service auslöst.

Fang mit a an — dem Aktionsmenü. Im Service-Inspect (nur dort) öffnet a eine einzige Liste mit jedem Editor und jeder Aktion, die das Overlay bietet — so musst du dir keine ~14 Tasten mit Groß-/Kleinschreibung merken (d/D, s/f, p/l/e, n/S/v, r/P, R, X). Das ist der auffindbare Weg ohne Shift — und kein abgespeckter: ein Eintrag schließt das Menü und öffnet genau das, was auch die direkte Taste öffnet, beide sind also gleichwertig und keine Varianten mit unterschiedlichem Verhalten. Die Einträge in ihrer Reihenfolge: Update image version… (Set image version…, wenn es keine neuere gibt — auf einen Tag deiner Wahl pinnen oder zurückgehen ist genauso legitim), Diff spec (previous → current), Roll back to the previous version, Why — placement diagnosis, Scale, Force-update, Edit ports, Edit labels, Edit env, Edit networks, Edit secrets, Edit mounts, Edit resources, Edit placement, Remove service — der destruktive, deshalb rot markiert — und Cancel. Navigiert wird mit j/k (g/G springen zum ersten/letzten Eintrag), Enter wählt aus; Esc — oder der Eintrag Cancel — schließt das Menü, ohne etwas zu tun. Die Image-Versionsauswahl steht an erster Stelle, weil eine Version zu setzen die gewöhnlichste Service-Änderung überhaupt ist; ihre direkte Taste u funktioniert weiterhin aus jeder Zeile des Inspect. Ist das Image eines, für das sich keine Version wählen lässt — eine Referenz ohne Tag, oder eine Registry, die die Tags des Repos nicht herausgibt, typischerweise eine private ohne Zugangsdaten —, bleibt der Eintrag in der Liste und sagt das, statt stillschweigend zu verschwinden, und verweist auf das docker service update --image, das funktioniert. Die direkte Taste zu jedem Eintrag:

TasteAktion
ddiff — zeigt ein Unified-Diff der aktuellen Spec des Service gegen seine vorherige (was das letzte Rolling Update geändert hat), unter Verwendung von Swarms PreviousSpec. Geänderte Felder werden feldweise gruppiert mit - removed / + added-Zeilen aufgelistet (Image, Modus/Replicas, Env, Labels, Netzwerke, Aliase, Secrets, Mounts, Ports, Placement-Constraints, Spread-Präferenzen, Ressourcen-Limits/-Reservierungen); unveränderte Felder werden weggelassen. Wenn der Service nie aktualisiert wurde (keine vorherige Spec), sagt es das. Schreibgeschütztes Overlay — j/k scrollen, Esc schließt
Rauf die vorherige Version zurückrollen (großes R, also Shift+r, um es von r Ressourcen-Limits zu unterscheiden) — das Gegenstück zum d-Diff darüber: es macht das letzte Rolling Update rückgängig. Der Bestätigungsdialog zeigt genau dieses Diff umgekehrt unter „This will undo:“, denn das ist es, was der Rollback tatsächlich ändert — eine Zeile, die das Update hinzugefügt hat, steht dort als - removed, und eine, die es entfernt hat, kommt als + added zurück. Die Vorschau ist auf 12 Einträge begrenzt, gefolgt von einem Hinweis „… and N more“, der für das vollständige Diff auf d verweist (haben sich nur Metadaten geändert, meldet er, dass es keine feldweisen Unterschiede gibt). Beim Bestätigen läuft ein serverseitiger Rollback — ein Manager-API-ServiceUpdate mit ServiceUpdateOptions{Rollback: "previous"}, kein clientseitiges Neuanwenden der PreviousSpec — der Manager führt ihn also selbst aus, beachtet dabei die Rollback-Konfiguration des Service (Parallelität, Verzögerung, Failure-Action) und meldet den Fortschritt als Update-Status rollback_started: genau das oben beschriebene Badge ↺ rolling back, auf der Baumzeile und in diesem Overlay. Ein Service, der nie aktualisiert wurde, hat keine vorherige Spec — du bekommst einen erklärenden Hinweis, keinen Fehler. Auch über das a-Aktionsmenü erreichbar. Wie d, D und X ist das eine feste Inspect-Taste, keine umbelegbare Keymap-Aktion
sscale — fragt nach einer neuen Replica-Anzahl und wendet sie an (nur replizierte Services; ein globaler Service meldet, dass er nicht skaliert werden kann)
fforce-update — deployt den Service neu, ohne seine Spec zu ändern (das Äquivalent zu docker service update --force: erhöht TaskTemplate.ForceUpdate), nach einer Bestätigung. Jede Task wird neu gestartet / neu geplant, womit du einen Service losbekommst, der in einem unvollständigen Zustand feststeckt (z. B. 1/2 Replicas). Ein ServiceUpdate (Rolling Update)
Xden Service entfernen (großes X) — löscht ihn dauerhaft und stoppt alle seine Tasks, hinter einer Bestätigung; kann nicht rückgängig gemacht werden. Ein Manager-API-ServiceRemove; bei Erfolg schließt sich das Inspect-Overlay und der Service-Baum aktualisiert sich
DPlacement diagnostizieren (großes D) — beantwortet warum ein Service nicht überall läuft, wo du es erwartest in einer Ansicht, statt ihn über mehrere docker-Befehle zu verfolgen. Für einen globalen Service listet es jeden Node mit ✓ laufend oder ✗ und dem Grund, warum er ausgeschlossen ist (Verfügbarkeit drain/pause, ein down-Zustand, ein nicht erfüllter Placement-Constraint oder eine Plattform-Diskrepanz) — deshalb kann ein 3-Node-Cluster legitim 2/2 anzeigen (der dritte Node ist nicht geeignet). Für einen replizierten Service listet es jede Task, die nicht läuft, mit der eigenen Meldung des Schedulers (Constraints, unzureichende Ressourcen, Image-Pull-Fehler, …). Constraints, die client-seitig nicht geprüft werden können (engine.labels.*), werden vermerkt, nicht geraten. Schreibgeschütztes Overlay
pveröffentlichte Ports bearbeiten — öffnet einen gestagten Listeneditor der Ports des Service, jeder in der Form PUBLISHED:TARGET[/proto] (proto tcp|udp|sctp, Standard tcp), z. B. 8080:80/tcp
lLabels bearbeiten — derselbe gestagte Listeneditor über key=value-Einträge
eUmgebungsvariablen bearbeiten — derselbe gestagte Listeneditor über die Env der ContainerSpec, jede als KEY=VALUE eingegeben (z. B. LOG_LEVEL=debug); der Schlüssel ist erforderlich, der Wert darf leer sein und selbst = enthalten. Wie Ports/Labels/Mounts erlaubt dieser Editor sehr wohl e zum Bearbeiten, sodass du einen Wert an Ort und Stelle anpassen kannst. Anders als die übrigen Editoren öffnet e hier den Eintrag in einem scrollbaren mehrzeiligen Textbereich (statt eines einzeiligen Feldes), sodass lange Werte wie GITLAB_OMNIBUS_CONFIG bequem zu bearbeiten sind — tippe frei (Enter fügt einen Zeilenumbruch ein), Ctrl-S speichert, Esc bricht ab. Das Anwenden ersetzt die Env des Service in einem ServiceUpdate (Rolling Update)
nNetzwerke bearbeiten — ein gestagter Listeneditor über die Netzwerkanschlüsse des Service; da ein Eintrag nur ein Name ist, ist er nur Hinzufügen/Entfernen (kein e Bearbeiten). Die Hinzufügen-Eingabe vervollständigt Netzwerknamen automatisch (schlägt Netzwerke vor, an die der Service noch nicht angeschlossen ist, oder tippe selbst eines). Das Anwenden ersetzt die Anschlüsse in einem ServiceUpdate. DNS-Aliase werden auch von hier aus bearbeitet: wähle in diesem Editor ein Netzwerk und drücke A, um eine gestagte Liste der DNS-Aliase des Service in diesem Netzwerk zu öffnen (a hinzufügen / e bearbeiten / d löschen / y kopieren / u rückgängig / w anwenden / Esc abbrechen); das Anwenden ersetzt die Aliase dieses Netzwerks in einem ServiceUpdate. Funktioniert auch, wenn der Service noch keine Aliase hat. (Du musst ein neu hinzugefügtes Netzwerk erst anwenden, bevor du seine Aliase setzen kannst.)
SSecrets bearbeiten (großes S, also Shift+s, um es von s Skalieren zu unterscheiden) — derselbe gestagte Listeneditor wie Netzwerke über die Secrets, die der Service referenziert; ein Eintrag ist nur ein Name, also nur Hinzufügen/Entfernen (kein e Bearbeiten). Die Hinzufügen-Eingabe vervollständigt Secret-Namen automatisch (schlägt Secrets vor, die der Service noch nicht verwendet, oder tippe selbst eines). Das Anwenden ersetzt die Secret-Referenzen des Service in einem ServiceUpdate (Rolling Update); jedes Secret wird unter /run/secrets/<name> eingehängt. Funktioniert auch, wenn der Service aktuell keine Secrets hat
vMounts bearbeiten — ein gestagter Listeneditor über die Volumes und Bind-Mounts des Service. Hinzufügen (a) oder Bearbeiten (e) öffnet ein kleines Formular statt eines einzelnen Textfeldes: eine Bind-Mount-Checkbox, eine Quelle (wenn die Box nicht angehakt ist, ist es ein Volume-Name mit Autocomplete bestehender Cluster-Volumes; wenn angehakt, ist es ein Host-Pfad), ein Container-Pfad und eine Read-only-Checkbox. (Intern ist jeder Eintrag weiterhin volume:NAME:TARGET[:ro] / bind:/host/path:TARGET[:ro].) Bind-Quellen und alle Ziele müssen absolute Pfade sein. Das Anwenden ersetzt die Mounts in einem ServiceUpdate (Rolling Update). Bind-Mount-Schutz (weich): wenn die gestagte Liste Bind-Mounts enthält, zeigt das Anwenden zuerst eine Warnung, die die Nodes auflistet, auf denen der Service eingeplant werden könnte (berechnet aus Placement-Constraints plus Node-Rolle/-Labels/-Verfügbarkeit), und erinnert dich daran, dass jede Bind-Quelle auf allen davon bereits existieren muss — swarmexec kann Host-Pfade nicht verifizieren (der Agent hat keinen Zugriff auf das Host-Dateisystem), daher werden Bind-Pfade nicht vervollständigt oder auf Existenz geprüft; es ist nur ein Hinweis
rRessourcen-Limits bearbeiten — ein kleines Formular, um die CPU- und Memory-Limits des Service (und optional Reservierungen) zu setzen, zu ändern oder zu löschen. CPU wird in Kernen angegeben (z. B. 0.5, 2); Memory als menschenlesbare Größe (z. B. 512m, 2g, 1.5GiB). Ein leeres Feld löscht dieses Limit (Swarm behandelt es dann als unbegrenzt). Eine Reservierung darf ihr Limit nicht überschreiten. Das Anwenden macht ein ServiceUpdate (Rolling Update); bestehende Pids-Limits und Device-/generische Reservierungen bleiben erhalten
PPlacement bearbeiten (großes P, also Shift+p, um es von p Ports zu unterscheiden) — öffnet ein kleines Menü mit zwei gestagten Editoren: Constraints und Spread-Präferenzen.
  • Constraints — die harten Placement-Constraints des Service (node.role, node.hostname, node.id, node.platform.os/arch, node.labels.<k>, engine.labels.<k>, jeweils mit == oder !=, z. B. node.role==manager, node.labels.zone!=eu). Die Hinzufügen-/Bearbeiten-Eingabe vervollständigt vollständig geformte Kandidaten aus dem aktuellen Cluster — Rolle, Hostname, Plattform und Node-/Engine-Labels jedes Nodes — sodass du einen Constraint meist auswählen kannst, statt ihn zu tippen (die ==-Form wird vorgeschlagen; tippe != von Hand für eine Negation). Die resultierende Node-Menge speist auch den Bind-Mount-Schutz des Mounts-Editors.
  • Spread-Präferenzen — die weichen --placement-pref spread=…-Strategien: eine gestagte, geordnete Liste bloßer Node-Attribute (z. B. node.labels.zone, node.hostname), über die Swarm Tasks gleichmäßig verteilt. Die Eingabe vervollständigt die Node-/Engine-Label-Schlüssel und Node-Attribute des Clusters automatisch (ein Deskriptor ist nur das Attribut — kein Operator oder Wert).
Das Anwenden ersetzt jeweils diesen Teil des Placements in einem ServiceUpdate (Rolling Update); der andere Teil bleibt erhalten.

Diese Editoren sind gestagt: a fügt einen Eintrag hinzu, d löscht den unter dem Cursor, y kopiert den ausgewählten Eintrag über OSC52 in deine Zwischenablage (sodass du z. B. eine einzelne Umgebungsvariable greifen kannst, während du sie ansiehst), u macht die letzte gestagte Änderung rückgängig (Hinzufügen / Bearbeiten / Löschen), solange du noch nicht angewendet hast — drücke es wiederholt, um schrittweise durch die gestagte Historie zurückzugehen — und w wendet alle Änderungen auf einmal in einem einzigen ServiceUpdate an (sodass z. B. das Hinzufügen mehrerer Volumes und anschließendes Drücken von w ein Rolling Update auslöst, nicht eines pro Eintrag), hinter einer Bestätigung. Wenn du Esc drückst, während noch Änderungen gestagt sind, verwirft der Editor sie nicht stillschweigend — er fragt, ob anwenden, verwerfen oder weiter bearbeiten. Solange Änderungen noch gestagt sind, werden sie hervorgehoben, sodass du genau siehst, was sich ändern wird: hinzugefügte oder bearbeitete Einträge erscheinen in Grün (neu) und entfernte Einträge bleiben als gedimmte rote „removed“-Zeilen stehen — die Hervorhebung verschwindet, sobald du anwendest. Die Editoren für Ports, Labels, Mounts, Env und Aliase (Aliase über A aus dem Netzwerk-Editor erreicht) haben außerdem e, um den Eintrag unter dem Cursor zu bearbeiten; die Editoren für Netzwerke und Secrets haben kein e — ein Netzwerk- oder Secret-Eintrag ist nur ein Name, also nur Hinzufügen (a) / Entfernen (d). Das e des Env-Editors öffnet einen scrollbaren mehrzeiligen Textbereich (Ctrl-S speichern / Esc abbrechen) für lange Werte; die übrigen Editoren behalten die einzeilige Eingabe mit Enter zum Bestätigen.

Der Baum aktualisiert sich automatisch alle ~10s, sodass ein im Hintergrund ersetzter Container oder ein skalierter Service von selbst auftaucht — der Cursor und alle ausgeklappten Services bleiben erhalten. r erzwingt weiterhin eine sofortige Aktualisierung.

Volumes-Tab

TasteAktion
/Suche — die Liste nach Volume-Name, Driver oder Node filtern
nein Volume erstellen — ein Formular mit Name, Driver (Standard local), Labels (k=v,k=v) und einem Node-Ziel (vervollständigt Node-Namen; leer lassen, um auf jedem Node zu erstellen, da Volumes node-lokal sind). Erstellt es auf dem Agent jedes Ziel-Nodes und meldet Erfolg/Fehler pro Node. Tab wechselt zwischen Feldern, Esc bricht ab
spacedas Volume aus-/abwählen (markiert ) für ein Massen-Löschen
aalle aktuell angezeigten Volumes aus-/abwählen
ddie ausgewählten Volumes — oder das unter dem Cursor — auf jedem Node löschen, der sie hält, hinter einer Bestätigung (mit einem Fortschritts-Overlay; die Löschungen laufen mit begrenzter Parallelität)
Pprune: jedes Volume löschen, das kein laufender Container einhängt und kein Service deklariert, hinter einer Bestätigung
Enterzeigen, welche Nodes das Volume halten; die Labels des Volumes werden schreibgeschützt über den Nodes aufgelistet (Docker hat keine Volume-Update-API, daher können Labels nach der Erstellung nicht bearbeitet werden — setze sie beim Erstellen des Volumes)
izeigen, welche Services/Container es verwenden
Adas Volume an einen Service anhängen: einen Service auswählen (Name-Autocomplete) → den Container-Zielpfad eingeben (absolut) → Attach oder Attach read-only wählen. Fügt den Mount über ein ServiceUpdate (Rolling Update) hinzu — das Volume-Gegenstück zu den Netzwerk-/Secret-Attach-Aktionen; das Umgekehrte lebt im v-Mounts-Editor des Service-Inspects
s Sdas Sortierfeld durchschalten (name → nodes → used → age → size) / umkehren

Das Löschen ist node-bewusst: ein Volume wird auf jedem Node entfernt, der es hält. Prune verschont Volumes, die ein Service deklariert, selbst wenn keine Task läuft, sodass es die Daten eines gestoppten Stacks nicht löscht. Für feinere Kontrolle öffnet Enter die Liste pro Node, wo space Nodes auswählt, d die Kopie des markierten Nodes löscht und a auf allen Nodes löscht — jeweils hinter einer Bestätigung.

Forwards-Tab

TasteAktion
Enterdie vollständigen Details des Forwards anzeigen (inklusive etwaiger Fehler)
dden ausgewählten Forward stoppen
odie URL des Forwards kopieren (http://127.0.0.1:<port>)

Ein laufender Container wird im Baum als local→remote annotiert (z. B. 9090→8080). Forwards laufen weiter, wenn ihr Overlay schließt und wenn du auf einen anderen Cluster wechselst; sie stoppen bei d oder wenn du die UI beendest. Die CLUSTER-Spalte nennt den Context, zu dem jeder gehört, gedimmt für den gerade sichtbaren — ein Forward auf einem anderen Cluster ist weiterhin deiner und lauscht weiter, aber sein CONTAINER und NODE benennen Dinge, die du von hier aus nicht sehen kannst. Die Baum-Annotation ist aus demselben Grund pro Cluster: eine Container-ID ist nur innerhalb ihres eigenen Daemons eindeutig.

Deshalb wird ein lokaler Port, den du schon benutzt, vorab abgelehnt — mit Angabe des Forwards, der ihn hält, und des Clusters, zu dem er gehört; der Prompt bleibt offen, damit du einen anderen Port wählen kannst. Das „address already in use" des Betriebssystems bleibt für den Fall, in dem es recht hat: ein Port, den ein anderes Programm hält und den swarmexec nicht benennen kann.

Networks-Tab

Eine Liste der Netzwerke des Swarms — Name, Driver, Scope, Typ (ingress / internal / attachable), ob das Netzwerk verschlüsselt ist und wie viele Services an jedes angeschlossen sind. Die TYPE-Spalte zeigt overlay oder den Driver-Namen (z. B. bridge) für einfache Netzwerke statt eines Strichs. Die ENC-Spalte zeigt 🔒 yes, wenn das Overlay-Netzwerk Data-Plane-Verschlüsselung an hat (erstellt mit --opt encrypted), sonst einen Strich. Der Name jedes Netzwerks und seine TYPE-Zelle sind nach Art farbcodiert (höchste Priorität zuerst):

TasteAktion
nein Netzwerk erstellen — öffnet ein Formular (Standard-Driver overlay) mit den gängigen Swarm-Optionen: attachable (standalone Container beitreten lassen), encrypted (Overlay-Data-Plane-Verschlüsselung), internal (kein externes Routing), IPv6, eine optionale MTU, ein optionales Subnet/Gateway (IPAM) und Labels (k=v,k=v). Tab wechselt zwischen Feldern, Enter auf Create bestätigt, Esc bricht ab. Erstellt es über ein Manager-API-NetworkCreate und aktualisiert dann den Tab
Enter / idie angeschlossenen Services zeigen, jeder mit seinen laufenden Containern darunter verschachtelt. Die eigenen Labels des Netzwerks werden schreibgeschützt oben aufgelistet (Docker hat keine Netzwerk-Update-API, daher können Netzwerk-Labels nach der Erstellung nicht bearbeitet werden — setze sie beim Erstellen des Netzwerks). Jeder Service-Header zeigt außerdem, wie viele DNS-Aliase er in diesem Netzwerk hat (oder no aliases)
Enterin der Members-Ansicht: die DNS-Aliase des ausgewählten Service aus-/einklappen (standardmäßig eingeklappt, damit die Liste kompakt bleibt); die Aliase erscheinen eingerückt unter dem Service-Header
Ain der Members-Ansicht: die DNS-Aliase des ausgewählten Service in diesem Netzwerk hinzufügen / bearbeiten — öffnet denselben gestagten Alias-Editor wie die Inspect-Ansicht (a hinzufügen / e bearbeiten / d löschen / w anwenden / Esc abbrechen); das Anwenden ersetzt die Aliase dieses Netzwerks in einem ServiceUpdate und aktualisiert die Ansicht. Funktioniert auch, wenn der Service noch keine Aliase hat. (A ist groß, Shift+a, um es von a Attach zu unterscheiden; standalone Nicht-Swarm-Container sind nicht bearbeitbar — Aliase sind ein ServiceUpdate pro Service)
ain der Members-Ansicht: einen Service an dieses Netzwerk anhängen — eine Autocomplete-Eingabe (schlägt Services vor, die noch nicht angeschlossen sind; du kannst auch einen beliebigen Namen tippen), dann eine Rolling-Update-Bestätigung
din der Members-Ansicht: einen Service von diesem Netzwerk trennen — eine Autocomplete-Eingabe (schlägt die aktuell angeschlossenen Services vor; du kannst auch einen beliebigen Namen tippen), dann eine Rolling-Update-Bestätigung

Anhängen oder Trennen macht ein Service-Update, das das Netzwerk in der Service-Spec hinzufügt oder entfernt, und aktualisiert dann den Networks-Tab. Das ist eine Manager-API-Operation (ServiceUpdate) — derselbe Kanal, den der Client für die Topologie nutzt — und sie löst ein Rolling Update aus, das die Tasks des Service neu startet.

Secrets-Tab

Eine schreibgeschützte Liste der Secrets des Swarms — Name, wie viele Services jedes verwenden, Alter, letzte Aktualisierung und Label-Anzahl. Secret-Werte werden nie angezeigt: die Docker-API legt sie nicht offen.

TasteAktion
Enterdie Metadaten des Secrets und die Services/Container zeigen, die es verwenden
ain der Detailansicht: das Secret an einen Service anhängen — eine Autocomplete-Eingabe (schlägt Services vor, die es noch nicht verwenden; du kannst auch einen beliebigen Namen tippen), dann eine Rolling-Update-Bestätigung
din der Detailansicht: das Secret von einem Service trennen — eine Autocomplete-Eingabe (schlägt Services vor, die es aktuell verwenden; du kannst auch einen beliebigen Namen tippen), dann eine Rolling-Update-Bestätigung

Das Anhängen fügt das Secret dem Service hinzu, eingehängt unter /run/secrets/<name> (wie docker service update --secret-add); das Trennen entfernt die Secret-Referenz. Dann aktualisiert sich der Secrets-Tab. Wie beim Netzwerk-Anhängen/-Trennen ist jedes ein Manager-API-ServiceUpdate, das ein Rolling Update auslöst, das die Tasks des Service neu startet.

Context-Sidebar

Die Cluster stehen in einer Spalte am rechten Rand, nicht in einem Tab. Den Cluster zu wechseln ist etwas, das man von dort aus tut, wo man gerade ist — und die Liste der Cluster ist das, was einem sagt, wo das ist. Sie gehört also auf den Bildschirm und nicht auf eine Seite, zu der man erst hinreisen muss.

c gibt ihr aus jedem Tab die Tastatur; c erneut oder Esc gibt sie an den Tab zurück, auf dem du warst. Der aktive Cluster ist mit markiert — derselbe Name, den der Footer zeigt. Ein Cluster, den du in dieser Sitzung schon besucht hast, trägt ein grünes ·: er ist noch verbunden, der Wechsel dorthin ist sofort da. Einer, der die Verbindung verweigert hat, trägt ein rotes . Der eingebaute default-Context ist geschützt.

Die Sidebar richtet ihre Breite nach dem längsten Context-Namen und tritt auf schmalen Terminals beiseite: unter etwa 92 Spalten blendet sie sich aus, statt den Baum abzuschneiden, und kommt zurück, solange c sie hält. Endpoints stehen nicht in der Spalte — dort ist kein Platz dafür, und i zeigt das vollständige Detail.

Ein Cluster-Wechsel behält deine Sitzung. Die UI baut sich nicht neu auf: wo der Cursor stand, welche Stacks und Services aufgeklappt waren und der /-Filter werden pro Cluster gemerkt — du landest also wieder genau dort, wo du aufgehört hast. Port-Forwards laufen weiter; der Forwards-Tab bekommt dafür eine CLUSTER-Spalte, damit auf einen Blick klar ist, welche zum gerade sichtbaren Cluster gehören und welche nicht.

Gepollt wird nur der Cluster, den du ansiehst: beim Wegschalten hört der andere auf zu aktualisieren, und seine ssh-Verbindung läuft kurz darauf von selbst aus. Der erste Wechsel zu einem Cluster dauert so lange, wie ihn zu erreichen dauert — die Verbindung wird aufgebaut, bevor sich auf dem Bildschirm etwas ändert. Scheitert sie, erfährst du, welcher Cluster es war, und bleibst auf dem, der funktioniert. Jeder weitere Wechsel zurück ist sofort da.

TasteAktion
cdie Sidebar aus jedem Tab fokussieren; c erneut oder Esc gibt die Tastatur zurück
iContext-Details — Endpoint und Jump-Hosts, für die in der Spalte kein Platz ist
u / Enterden ausgewählten Context aktivieren — macht ihn zum aktuellen (auch für dockers gespeicherten current context) und schaltet die UI auf diesen Cluster um, mit deiner Position, deinen Filtern und deinen Port-Forwards
neinen Context erstellen — ein geführtes Formular. Eine Checkbox „Connect to Docker over SSH“ entscheidet den Endpoint: wenn an, füllst du SSH-User / Host / Port aus, und eine zweite Checkbox „Use a jump host“ zeigt ein Feld Jump-Host(s) (komma-separiert, Multi-Hop-ProxyJump) — swarmexec speichert sie am Context und injiziert -J in beide ssh-Verbindungen, die Docker-API und den Agent-Tunnel, sodass Bastions ohne Bearbeiten von ~/.ssh/config funktionieren. Wenn aus, gibst du einen einfachen tcp:// / unix://-Host ein. Das Formular setzt den Docker-Host für dich zusammen, und Test pingt den zusammengesetzten Endpoint, bevor du speicherst. Auch auf der CLI: swarmexec context create <name> --docker-host ssh://ops@mgr --ssh-jump bastion1,bastion2
dden ausgewählten Context entfernen (hinter einer Bestätigung); das Entfernen des aktuellen setzt die Auswahl auf default zurück

Nodes-Tab

Listet die Nodes des Swarms mit allgemeinen Infos und ein paar Aggregationen — NODE (Hostname; Manager in aqua, der Leader markiert mit ), ROLE (manager / worker), AVAIL (active / pause / drain, farbcodiert), STATE (ready / down), ENGINE-Version, TASKS (auf dem Node eingeplante laufende Tasks), VOLS (Volumes, die der Node hält — node-lokal, füllt sich daher einen Moment nach dem Rest) und LABELS (Anzahl).

TasteAktion
Enter / iNode-Details — ein schreibgeschütztes Overlay mit Hostname, ID, Rolle/Leader, Verfügbarkeit, State, Adresse, Engine, Plattform, CPUs, Memory, Anzahl laufender Tasks, den Blöcken reservierte Ressourcen, tatsächliche Nutzung und Images auf diesem Node (siehe unten), Volume-Anzahl und der vollständigen Label-Liste
ldie Labels des ausgewählten Nodes bearbeiten — ein gestagter Listeneditor (a hinzufügen / e bearbeiten / d löschen / w anwenden / Esc abbrechen) über key=value-Einträge. Das Anwenden macht ein NodeUpdate — anders als bei einem Service greift ein Node-Update sofort (kein Rolling Update). Node-Labels werden häufig als Ziele von Placement-Constraints verwendet (node.labels.<k>)
adie Verfügbarkeit des Nodes setzen — ein Menü aus Active / Pause / Drain, das den aktuellen Zustand des Nodes markiert. Active plant Tasks hier normal ein; Pause behält die laufenden Tasks und verhindert nur neue Platzierungen; Drain verschiebt jeden Task vom Node herunter. Active und Pause greifen direkt; Drain läuft über eine Bestätigung, die nennt, wie viele laufende Tasks der Swarm stoppen und anderswo neu einplanen wird (Services, deren Tasks nirgendwo sonst platziert werden können, werden unschedulable). Wie eine Label-Änderung ist es ein NodeUpdate (Read-Modify-Write auf der Node-Spec) und greift sofort — Nodes haben kein Rolling Update. Umlegbar als node_availability
PImage-Plattenplatz auf dem Node freiräumen (großes P, also Shift+p — dieselbe Geste, die der Volumes-Tab für sein Prune verwendet). Öffnet ein kleines Menü mit den zwei unten beschriebenen Modi, jeder hinter einer eigenen Bestätigung; es lohnt sich, vorher zu lesen, welcher welcher ist. Die Fußzeile endet mit P reclaim images, das ?-Overlay führt es als reclaim image disk space on the node. Umlegbar als node_prune_images

Reservierte Ressourcen. Die Node-Details enthalten einen Block reserved by tasks (scheduler's view): je für CPU und Memory einen Balken plus reserviert / Kapazität und wie viel frei ist. Der Balken ist grün, wird ab 75 % gelb und bei 100 % oder darüber rot; eine Überbuchung wird ausdrücklich benannt, und „frei“ wird nie negativ. Ein Node, der keine Kapazität meldet, sagt das, statt einen Balken zu zeichnen. Der Block kostet keinen zusätzlichen API-Call — die Node-Liste holt die Task-Liste ohnehin.

Das sind Reservierungen, keine Auslastung. Die Zahlen sind die Summe der von den Tasks deklarierten Reservations — genau die Rechnung, die der Swarm-Scheduler selbst anstellt, wenn er entscheidet, ob ein Task auf einen Node passt, und der übliche Grund, warum ein Service unschedulable bleibt. Der tatsächliche CPU-/RAM-Verbrauch ist node-lokal und nicht, was hier steht — er hat direkt darunter einen eigenen Block. Tasks ohne deklarierte Reservierung werden gezählt und separat ausgewiesen: Sie buchen hier nichts, können aber trotzdem den ganzen Node auslasten — ein niedriger Prozentwert heißt also nicht, dass der Node im Leerlauf ist, und in den meisten Clustern setzen ohnehin nur wenige Services überhaupt Reservierungen. Ein Task hält seine Reservierung ab dem Moment der Zuweisung bis zu einem Endzustand: Tasks in preparing oder starting zählen also mit, shutdown / failed / complete / rejected dagegen nicht (der Swarm behält diese als Historie in der Task-Liste).

Tatsächlich von Containern genutzt. Darunter steht ein zweiter Block — in use by containers (measured on the node) — mit denselben proportionalen Balken: was die Container des Nodes gerade wirklich verbrauchen, CPU in Cores und Memory, jeweils gegen die Kapazität des Nodes. Die beiden Blöcke bleiben bewusst getrennt, denn sie beantworten verschiedene Fragen und liegen regelmäßig weit auseinander: Ein Node kann voll gebucht und trotzdem im Leerlauf sein — oder kaum gebucht und am Limit. Die Zahlen kommen vom Agent dieses Nodes, derselben Quelle wie die Nutzungsmarker im Service-Baum — solange die CPU-Differenz noch nicht vorliegt, steht dort measuring…, und ein Node, dessen Agent nicht erreichbar oder zu alt ist, bekommt gar keinen Block statt einer Reihe Nullen, die wie „im Leerlauf“ aussähe.

Nur Container. Gezählt werden die Container des Nodes — Kernel, Docker-Daemon und alles, was außerhalb von Docker läuft, stecken nicht in dieser Zahl. Es ist nicht die Load des Nodes, und der Block sagt das selbst dazu.

Images auf diesem Node. Zuletzt steht in den Details ein Block images on this node: wie viel der Layer-Store des Nodes belegt und in wie vielen Images, und wie viel davon in ungetaggten Überbleibseln freigeräumt werden kann (ein Node ohne ungetaggte Images sagt das, statt eine Null anzubieten). Gibt es darüber hinaus etwas, kommt eine weitere Zeile dazu: wie viel zusätzlich getaggt, aber ungenutzt ist — bewusst als Kosten formuliert, das Entfernen bedeutet, diese Images wieder zu ziehen, und nicht als Angebot. Den Block gibt es, weil Images die eine node-lokale Ressource sind, die von der Cluster-Seite überhaupt nicht zu sehen ist: Die Manager-API hat keinerlei Sicht auf Images — anders als bei Volumes, von denen sie immerhin weiß, dass sie gemountet sind. Volumes ließen sich aus swarmexec längst clusterweit verwalten; ein Node, dessen Platte still mit alten Layern volllief, war nicht einmal sichtbar. Der Block lädt eigenständig, parallel zur Node-Liste, und erscheint daher einen Moment nach dem Rest des Overlays.

Freiräumen — P im Nodes-Tab. Das Menü dahinter hat zwei Einträge, bewusst keine Aktion mit Checkbox, denn es sind zwei verschiedene Handlungen, und eine Checkbox lädt dazu ein, versehentlich die zerstörerische zu erwischen:

EintragWas entfernt wird
Untagged leftoversdie Layer, die Rebuilds zurücklassen und aus denen sich nichts starten lässt. Sicher: Das kann keinen Service stoppen und keinen Pull erzwingen
Every unused imageentfernt zusätzlich getaggte Images, die gerade jetzt kein Container benutzt. Auf einem Swarm-Node gehören dazu jeder Service, der aktuell auf null skaliert ist, und jede Task zwischen zwei Restarts — jede davon muss ihr Image erneut ziehen
Lies den zweiten Eintrag nicht als „dasselbe, nur mehr“. „Every unused image“ entfernt Images, die zu etwas gehören, das bloß in diesem Augenblick nicht läuft. Ein auf null skalierter Service, eine neu startende Task, ein gestern gestoppter Stack — sie alle verlieren ihr Image und müssen es wieder ziehen, um zurückzukommen, und wenn die Registry nicht erreichbar ist, kommen sie nicht zurück. Jeder der beiden Einträge läuft über eine eigene Bestätigung, die genau diese Konsequenz benennt und die Bytes nennt, die er freiräumen würde; es sind getrennte Texte, gerade damit keiner anfangen kann, den anderen zu beschreiben.

Zwei Modi, zwei Berechtigungen. Der Agent autorisiert sie als zwei eigenständige Aktionen — image.prune für die ungetaggten Überbleibsel und image.prune.all für den Rundumschlag —, sodass eine Policy das Freiräumen ungetaggter Layer erlauben kann, ohne den zerstörerischen Modus mit zu erlauben. Jedes Prune landet im Audit-Log des Agents: mit welchem Modus, wie viel freigeräumt wurde und wie viele Images gegangen sind.

Woher die Zahlen kommen. Sie stammen aus derselben Quelle, aus der auch docker system df berichtet, und stimmen exakt mit ihm überein — auf einem echten Node nachgemessen bei 56 Images, 32,70 GB auf Platte, 22,99 GB freiräumbar. Insbesondere summiert swarmexec nicht die Größen der einzelnen Images: Die Größe eines Images enthält jeden Layer, aus dem es gebaut ist, und Layer werden geteilt — diese Summe meldet also deutlich zu viel; in der Entwicklung behauptete sie 32,7 GB, wo der Daemon 22,2 GB sagte. Eine Einschränkung, die dazugehört: Der Wert für nur ungetaggt ist eine Untergrenze — ein Layer, den sich zwei ungetaggte Images teilen, gehört zur Unique-Size von keinem der beiden —, ein Prune der ungetaggten Images räumt also womöglich etwas mehr frei als angekündigt, nie weniger.

Dieselbe Voraussetzung wie bei den Nutzungs- und Health-Zahlen oben: Es braucht Agents aus diesem Release oder neuer. Ein Node mit älterem Agent zeigt schlicht gar keinen Image-Block und meldet auf P, dass er nicht geantwortet hat — ausrollen mit swarmexec init --force.

Configs-Tab

Tab 8 — das Gegenstück zum Secrets-Tab für das andere Objekt, das der Swarm in Container reicht, mit einem entscheidenden Unterschied: der Inhalt eines Configs ist lesbar. Der Secrets-Tab kann einen Wert nie zeigen (die Docker-API gibt ihn nicht heraus); die Config-Detailansicht zeigt den tatsächlichen Inhalt, sodass du siehst, was ein Service wirklich bekommt, ohne auf docker config inspect auszuweichen. Genau darum geht es in diesem Tab.

Die Liste ist schreibgeschützt: CONFIG (Name), USED BY (wie viele Services es einhängen), SIZE, AGE, UPDATED und LABELS (Anzahl).

TasteAktion
Enter / idie Detailansicht öffnen: Name, ID, Größe, erstellt, aktualisiert, die Services, die das Config einhängen, seine Labels — und den Inhalt des Configs
j k / g Gin der Detailansicht: scrollen · an den Anfang / ans Ende springen (Esc schließt)

Der Inhalt wird bei Bedarf geholt, wenn die Detailansicht aufgeht — nicht zusammen mit der Liste — denn ein Config kann eine ganze nginx.conf sein; bis dahin steht dort loading…, ein leeres Config meldet (empty). Gezeigt wird eine Vorschau, begrenzt sowohl auf 64 KiB als auch auf 500 Zeilen. Beide Grenzen sind nötig: Begrenzt man nur die Bytes, kann eine Datei aus sehr kurzen Zeilen immer noch zu Zehntausenden gerenderten Zeilen werden — jede zusätzlich eingerückt — und genau das lässt die Ansicht kriechen. Eine Kürzung wird immer benannt („… truncated at 500 lines“ bzw. an der Byte-Grenze), nie still abgeschnitten, und binärer Inhalt wird gemeldet, nicht ausgekippt („(binary content, N — not shown)“, erkannt an NUL-Bytes im Kopf der Daten). Das vollständige Config ist ein docker config inspect entfernt.

USED BY wird genauso hergeleitet wie im Secrets-Tab: Der Swarm führt keinen Rückwärtsindex von einem Config zu seinen Nutzern, also liest swarmexec die Service-Specs (ein ServiceList) und erkennt eine Referenz an der ID oder am Namen des Configs — eine Spec kann beide Formen enthalten, deshalb werden beide nachgeschlagen und zusammengeführt. Best effort: Lässt sich die Service-Liste nicht lesen, erscheinen die Configs trotzdem, nur ohne Zuordnung.

Eingebettete Shell & Logs

In einer eingebetteten Shell löst Ctrl-] ab (ohne den Prozess zu beenden). Die Logs-Ansicht — ein einzelner Container oder die aggregierten Logs eines Service — unterstützt dasselbe formatbewusste Parsen und Filtern wie der logs-Befehl:

TasteAktion
fFollow umschalten
FLog-Format durchschalten (classic → json → logfmt → gelf → raw)
lMindeststufen-Filter durchschalten (off → trace → … → fatal → off)
/ein Grep-Regexp auf die Nachricht eingeben (leer löscht es)
mMaus-Capture umschalten (aus = das eigene Auswählen/Kopieren deines Terminals)
scrollen
Esc / qschließen

Die Ansicht rendert die gepufferten Zeilen live neu, wenn du Format, Stufe oder Grep änderst, und die Titelleiste zeigt den aktiven Status fmt:… lvl:… grep:….

Beim Folgen verbindet sich die Log-Ansicht über Container-Ersetzung hinweg neu, genau wie es der logs-Befehl tut — sowohl für die Logs eines einzelnen Containers als auch für die aggregierten Logs eines Service (jedem Replica nach Slot gefolgt). Reconnect-Hinweise erscheinen inline in der Log-Ansicht.

Log-Viewer

Drücke ` (Backtick) von jedem Tab aus, um ein Live-Log-Overlay umzuschalten. Es zeigt die neuesten Einträge aus dem In-Memory-Ringpuffer — neueste unten — aktualisiert sich, während es offen ist, und ist nach Stufe farbcodiert (rot = error, gelb = warn, grau = debug). / scrollen; Esc, q oder ` schließen es. Das ist das Fenster der TUI auf dieselben Logs, die in die Log-Datei gehen, da das Terminal sie selbst nicht anzeigen kann, während die UI zeichnet.

Reaktionsfähigkeit. Die Containerliste aktualisiert sich außerhalb des UI-Threads (ihre zwei Manager-API-Aufrufe liefen früher inline und konnten die TUI kurz einfrieren), und ein Hintergrund-Watchdog loggt eine Warnung, wenn die Event-Loop hängt — praktisch, um Trägheit aus dem Log-Viewer zu diagnostizieren.

Eigene Tasten

Die Shortcut-Tasten sind in ~/.config/swarmexec/keys.yaml umbelegbar (neben config.yaml; überschreibe den Pfad mit $SWARMEXEC_KEYS). Jeder Wert ist eine einzelne Taste oder das Wort space. Die strukturellen Tasten — Enter, Esc, Tab, die Pfeile, die Tab-Nummerntasten 18 und die vim-Aliase j/k/g/G — sind fest. Der Footer zeigt immer deine tatsächlichen Tasten, und das ?-Overlay listet sie alle auf.

# ~/.config/swarmexec/keys.yaml — omit any line to keep its default quit: q refresh: r copy: y toggle_mouse: m search: / fold: h unfold: l forward: p container_inspect: i logs: L security_risks: "!" # "!" muss in Anführungszeichen — YAML stack_group: s volume_select: space volume_select_all: a volume_delete: d volume_prune: P volume_used_by: i volume_attach: A volume_sort: s volume_sort_reverse: S volume_new: n network_attached: i network_new: n context_use: u context_new: n context_delete: d node_edit_labels: l node_availability: a node_prune_images: P secret_delete: d secret_new: n forward_stop: d forward_copy_url: o

Das Laden schlägt nie fehl: eine ungültige oder reservierte Taste, eine unbekannte Aktion oder zwei Aktionen, die auf einem Tab an dieselbe Taste gebunden sind, fallen jeweils auf den Standard zurück, und die UI zeigt die Warnungen einmalig beim Start.

11. Exit-Codes

CodeBedeutung
0Erfolg
2Nutzungs-/Flag-/Konfigurationsfehler oder eine abgebrochene interaktive Auswahl
125Transportfehler — nicht erreichbarer Manager oder Agent, Dial-/TLS-Fehler, Stream-Fehler, abgebrochene Bestätigung (spiegelt Dockers 125)
Nbei exec der eigene Exit-Code des Remote-Befehls ungleich null, unverändert weitergegeben