Docker-Befehle: Container starten und wiederfinden
Die Docker-Befehle für den vServer-Alltag: Container starten, wiederfinden, Logs lesen, hineinspringen und wieder aufräumen. Mit Erklärung zu -d, --name, -p und -v.
Das Arbeitsset
Für den Alltag auf einem vServer brauchst du etwa zehn Docker-Befehle: docker run, docker ps -a, docker logs, docker exec, docker start, docker stop, docker rm, docker images, docker inspect und docker system df. Wenn du gerade eine docker run ...-Zeile aus einem README kopiert hast und den Container jetzt nicht mehr findest, ist docker ps -a der Befehl, der ihn dir zeigt, auch wenn er längst gestoppt ist. Der Rest dieser Seite erklärt, wann du welchen Befehl nimmst und warum ein Container manchmal weg ist, obwohl du ihn eben noch gestartet hast.
Ein Container ist ein einzelner Prozess auf deinem Server, den der Kernel in ein eigenes Dateisystem und ein eigenes Netzwerk sperrt. Darin läuft kein zweiter Kernel, deshalb startet ein Container in Sekundenbruchteilen und deshalb ist er auch sofort beendet, sobald sein Hauptprozess endet.
Brauchst du sudo?
Viele vServer-Panels loggen dich direkt als root ein. Dann lässt du bei allen Befehlen auf dieser Seite das sudo einfach weg. Arbeitest du als normaler Benutzer, stellst du jedem Befehl sudo voran, oder du nimmst deinen Benutzer einmalig in die Gruppe docker auf.
sudo usermod -aG docker $USER
# danach ausloggen und neu einloggen, sonst greift die neue Gruppe nicht
id -nGFehlt beides, bricht jeder Docker-Befehl mit permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock ab. Achte darauf: die Gruppe docker ist praktisch gleichbedeutend mit Root-Rechten auf dem Host, weil du über den Socket jedes Verzeichnis des Servers in einen Container einhängen kannst. Das ist Bequemlichkeit, kein Sicherheitsgewinn. Falls Docker auf deinem Server noch gar nicht läuft, richtest du es zuerst nach der Einrichtung von Docker auf einem VPS ein.
Docker-Container starten: run, start, exec und attach
Das ist die Unterscheidung, an der am Anfang fast alle hängen bleiben. Vier Befehle klingen ähnlich, und drei davon machen etwas völlig anderes als erwartet.
docker run erstellt jedes Mal einen neuen Container aus einem Image. Es ist kein "Start"-Befehl für etwas, das schon existiert. Rufst du dieselbe Zeile zweimal auf, versuchst du, einen zweiten Container mit demselben Namen anzulegen, und Docker bricht mit einer Meldung ab, die Conflict. The container name "/web" is already in use by container enthält. Genau das ist der Moment, in dem die meisten anfangen, Namen wie web2 zu vergeben, obwohl der erste Container völlig in Ordnung ist.
docker run -d --name web -p 8080:80 nginx:1.27-alpine
docker ps
docker stop webdocker start weckt einen vorhandenen, gestoppten Container wieder auf. Der Container behält seinen Namen, seine Ports, seine Volumes und sein Dateisystem, weil docker stop nichts löscht. Es beendet nur den Prozess darin.
docker start web
docker restart webdocker exec startet einen zusätzlichen Prozess in einem Container, der gerade läuft. Der Hauptprozess bleibt dabei unangetastet. Auf einen gestoppten Container kannst du nicht execen, weil dort kein Namespace mehr existiert, in den der neue Prozess gehen könnte. Die Fehlermeldung nennt dann den Zustand des Containers direkt.
docker attach hängt dein Terminal an den Hauptprozess mit PID 1. Es wird nichts Neues gestartet. Drückst du dort Strg+C, bekommt der Hauptprozess ein SIGINT, und der Container endet in den meisten Fällen. Lösen kannst du dich ohne Beenden mit Strg+P gefolgt von Strg+Q, und auch das klappt nur, wenn der Container mit -it gestartet wurde. Für den Alltag brauchst du attach fast nie: docker logs -f zeigt dieselbe Ausgabe, ohne dass ein Tastendruck den Dienst beenden kann.
Kurz gefasst: run erzeugt, start weckt auf, exec steigt ein, attach hört dem Hauptprozess zu.
Container wiederfinden: docker ps und docker ps -a
docker ps listet nur Container, die in diesem Moment laufen. Ein Container, der beim Start abgestürzt ist, steht dort nicht, und deshalb entsteht der Eindruck, er sei nie angelegt worden. docker ps -a listet alle, auch die beendeten.
docker ps -a
docker ps -a --filter name=web
docker ps -a --filter status=exitedSchau in die Spalte STATUS. Beginnt der Eintrag mit Up, läuft der Container, und dahinter steht die Laufzeit. Beginnt er mit Exited, ist der Hauptprozess beendet, und die Zahl in Klammern ist sein Exit-Code. Ein Exit-Code ungleich 0 heißt, dass der Prozess mit einem Fehler ausgestiegen ist, und die Begründung dafür steht in den Logs. Ein Exit-Code 0 heißt dagegen, dass das Programm sauber fertig geworden ist, was bei kurzlebigen Images völlig normal ist.
In der Spalte NAMES steht der Name, den du mit --name vergeben hast. Ohne --name erfindet Docker einen Namen wie nostalgic_bohr, und genau deshalb lohnt sich --name ab dem ersten Container.
Logs lesen, auch bei einem toten Container
docker logs web
docker logs -f --tail 50 web
docker logs --since 15m webdocker logs gibt dir das, was der Hauptprozess auf stdout und stderr geschrieben hat, und zwar auch dann noch, wenn der Container schon beendet ist. Bei einem abgestürzten Dienst suchst du nach der letzten Zeile vor dem Ende: dort steht die fehlende Umgebungsvariable, die falsche Datenbank-Adresse oder die Datei, die nicht lesbar war.
-f folgt dem Log fortlaufend. Ein Strg+C beendet hier nur das Mitlesen und nicht den Container, weil logs ein eigener Client-Prozess auf deinem Host ist.
Bleibt docker logs leer, obwohl der Dienst läuft, schreibt die Anwendung ihre Logs wahrscheinlich in eine Datei im Container statt nach stdout. Dann kommst du mit docker exec an diese Datei heran.
In den Container hinein: docker exec -it
docker exec -it web sh
docker exec -it db bash
docker exec web nginx -t-i hält die Standardeingabe offen, -t gibt dir ein Terminal. Ohne beides bekommst du keine brauchbare Shell, sondern einen Prozess, der sofort wieder endet. Bei Images auf Alpine-Basis gibt es kein bash, der Versuch endet mit OCI runtime exec failed: exec: "bash": executable file not found in $PATH. In diesem Fall nimmst du sh.
Alles, was du in dieser Shell installierst oder änderst, ist beim nächsten docker run mit demselben Image wieder verschwunden, weil run ein frisches Dateisystem aus dem Image anlegt. Dauerhaft ist nur, was in einem Volume liegt. Behandle die Shell im Container also als Werkzeug zum Nachschauen, nicht als Ort zum Konfigurieren.
Die Flags bei docker run, die wirklich zählen
-dstartet den Container im Hintergrund und gibt dir die Shell zurück. Ohne-dhängt dein Terminal am Container, und das Schließen der SSH-Sitzung beendet ihn.--name webgibt dem Container einen Namen, den du dir merken kannst. Jeder weitere Befehl auf dieser Seite akzeptiert diesen Namen anstelle der Container-ID.-p 8080:80veröffentlicht einen Port. Links steht der Port auf dem Server, rechts der Port im Container. Ist der Host-Port schon belegt, enthält die FehlermeldungBind for 0.0.0.0:8080 failed: port is already allocated.-v daten:/var/lib/mysqlhängt ein benanntes Volume ein, damit die Daten eindocker rmüberleben.--restart unless-stoppedsorgt dafür, dass der Container nach einem Absturz und nach einem Neustart des Servers wieder hochkommt, solange du ihn nicht selbst gestoppt hast.
Die Restart-Policy kannst du bei einem bestehenden Container nachrüsten, dafür musst du ihn nicht neu anlegen:
docker update --restart unless-stopped web
systemctl is-enabled dockerDer zweite Befehl ist wichtig, weil keine Restart-Policy greift, wenn der Docker-Dienst selbst beim Booten nicht startet. Für Setups aus mehreren Containern ist der automatische Start nach dem Reboot separat beschrieben.
Warum ein veröffentlichter Port trotz ufw erreichbar ist
Ein mit -p veröffentlichter Port ist von außen erreichbar, auch wenn ufw status ihn als gesperrt anzeigt, weil Docker eigene Regeln in die nat-Tabelle schreibt und dein Paket dadurch vor den Filterregeln von ufw umgeleitet wird. Die vollständige Erklärung und die Gegenmaßnahmen stehen in warum Docker-Ports die ufw-Firewall umgehen. Für heute reicht eine Gewohnheit: solange kein Reverse Proxy davor steht, bindest du mit -p 127.0.0.1:8080:80 nur an localhost, dann ist der Port ausschließlich vom Server selbst aus erreichbar.
Volumes, und wem die Dateien darin gehören
docker volume ls
docker run -d --name web -p 127.0.0.1:8080:80 -v /srv/web/html:/usr/share/nginx/html:ro nginx:1.27-alpineEin benanntes Volume (-v daten:/pfad) verwaltet Docker selbst. Ein Bind-Mount (-v /srv/web/html:/pfad) zeigt auf ein echtes Verzeichnis deines Servers. Das angehängte :ro hängt es schreibgeschützt ein.
Bei Bind-Mounts kommt der häufigste Folgefehler: Der Prozess im Container läuft unter einer anderen UID als dein Benutzer auf dem Host, deshalb kann er in ein Verzeichnis, das dir gehört, nicht schreiben, und der Dienst startet mit permission denied nicht durch. Wie du die passende UID setzt, steht in der Erklärung zu PUID und PGID.
Images: pull, ls und feste Tags
docker pull nginx:1.27-alpine
docker images
docker rmi nginx:1.26-alpineEin Image ist die Vorlage, ein Container die laufende Instanz davon. docker images zeigt dir in den Spalten REPOSITORY und TAG, welche Vorlagen auf dem Server liegen, und in SIZE, wie viel Platz sie belegen.
Nutze feste Tags wie nginx:1.27-alpine statt nginx:latest. latest ist kein Versprechen auf Aktualität, sondern nur der Tag, den Docker nimmt, wenn du keinen angibst. Er zeigt heute auf eine Version und in sechs Monaten auf eine andere, deshalb kann derselbe Befehl später eine Software mit geänderter Konfiguration holen und dein Setup ohne dein Zutun brechen. Mit einem festen Tag entscheidest du selbst, wann du aktualisierst.
Stoppen, entfernen, aufräumen
docker stop web
docker rm web
docker rm -f web
docker run --rm -it alpine:3.21 shdocker stop schickt dem Hauptprozess ein SIGTERM und wartet zehn Sekunden auf ein sauberes Ende, danach folgt SIGKILL. Braucht eine Datenbank länger zum Herunterfahren, gibst du ihr mit docker stop -t 30 db mehr Zeit, sonst riskierst du einen unsauberen Zustand auf der Platte.
docker rm löscht einen Container samt seinem Schreib-Layer, funktioniert aber nur, wenn er gestoppt ist. docker rm -f stoppt und löscht in einem Schritt. Benannte Volumes bleiben dabei bestehen, weil sie nicht zum Container gehören, deine Daten überleben also ein rm.
--rm beim Start ist der Gegenentwurf: Der Container löscht sich selbst, sobald er endet. Das ist der richtige Weg für kurze Tests, damit sich keine beendeten Container ansammeln.
Wenn du nicht weißt, was los ist: inspect und stats
docker inspect --format '{{.State.Status}}' web
docker inspect --format '{{.State.ExitCode}}' web
docker inspect --format '{{json .NetworkSettings.Ports}}' web
docker inspect --format '{{json .Mounts}}' web
docker stats --no-streamdocker inspect ohne --format liefert dir das vollständige JSON zu einem Container, und das ist am Anfang zu viel. Mit --format fragst du genau ein Feld ab. Die vier Zeilen oben beantworten die vier Fragen, die tatsächlich vorkommen: Läuft er, womit ist er ausgestiegen, welche Ports sind wirklich veröffentlicht, und welche Verzeichnisse hängen drin. Die Port-Abfrage ist besonders nützlich, wenn du dir nicht sicher bist, ob du Host- und Container-Port vertauscht hast.
docker stats zeigt Auslastung fortlaufend, --no-stream gibt dir eine einzelne Momentaufnahme, die sich gut in ein Skript packen lässt. Achte auf die Spalte MEM USAGE / LIMIT. Hast du kein Speicherlimit gesetzt, steht rechts der gesamte RAM des Servers, und das heißt: ein einzelner Container kann deinen vServer vollständig auslasten.
Platz auf der Platte zurückholen
docker system df
docker system df -v
docker image prune -adocker system df zeigt dir, wie viel Platz Images, Container, Volumes und der Build-Cache belegen, und in der Spalte RECLAIMABLE, wie viel davon frei wäre. Alte Images sind auf einem kleinen vServer mit Abstand der größte Posten, weil jedes docker pull eine weitere Version daneben legt.
Sei bei docker system prune -a --volumes vorsichtig: Diese Variante entfernt auch Volumes, an denen kein laufender Container hängt, und damit echte Daten. Welche prune-Befehle sicher sind und welche nicht, steht in der Anleitung zum Aufräumen von Docker-Speicherplatz.
Ab zwei Containern hört diese Seite auf
Sobald zwei Dienste zusammenarbeiten sollen, hör auf, docker run-Zeilen zu sammeln, und schreib eine compose.yaml. Die Befehle dafür stehen im Docker-Compose-Cheat-Sheet.
FAQ
Warum ist mein Container nach docker run sofort wieder weg?
Ein Container lebt genau so lange wie sein Hauptprozess. Endet dieser Prozess, endet der Container, und docker ps zeigt ihn nicht mehr an. Finde ihn mit docker ps -a und lies die Spalte STATUS: Der Exit-Code in den Klammern sagt dir, ob das Programm sauber fertig wurde (0) oder mit einem Fehler abgebrochen ist. Danach schaust du mit docker logs <name> in die letzten Zeilen vor dem Ende, denn docker logs funktioniert auch bei beendeten Containern.
Was ist der Unterschied zwischen docker run und docker start?
docker run legt jedes Mal einen neuen Container aus einem Image an. docker start startet einen Container, den es schon gibt, mit seinem alten Namen, seinen Ports und seinen Daten. Deshalb scheitert ein zweites docker run mit derselben Zeile an einer Meldung mit Conflict. The container name ... is already in use. Nach einem docker stop willst du in fast allen Fällen docker start <name>.
Wie komme ich in einen laufenden Container hinein?
Mit docker exec -it <name> sh, oder bash statt sh, wenn das Image es mitbringt. Bei Alpine-basierten Images gibt es kein bash, der Versuch endet mit executable file not found in $PATH. Nutze nicht docker attach dafür: Das hängt dich an den Hauptprozess, und Strg+C beendet dort den Dienst statt deiner Sitzung.
Startet mein Container nach einem Neustart des vServers wieder?
Nur mit einer Restart-Policy. Starte ihn mit --restart unless-stopped oder setze die Policy nachträglich mit docker update --restart unless-stopped <name>. Prüfe zusätzlich mit systemctl is-enabled docker, ob der Docker-Dienst selbst beim Booten startet, denn ohne laufenden Daemon greift keine Policy.
Verliere ich meine Daten, wenn ich einen Container lösche?
Alles, was nur im Dateisystem des Containers liegt, ist nach docker rm weg, weil rm den Schreib-Layer entfernt. Benannte Volumes und Bind-Mounts bleiben erhalten, denn sie gehören nicht zum Container. Prüfe vorher mit docker inspect --format '{{json .Mounts}}' <name>, was tatsächlich außerhalb liegt, und lass bei docker system prune die Option --volumes weg, solange du nicht genau weißt, welche Volumes du entfernst.