Docker startet Anwendungen als Container aus fertigen Images, und Docker Compose beschreibt mehrere Container samt Daten und Netzwerk in einer einzigen Datei namens docker-compose.yaml. Hier lernst du die Grundbegriffe, installierst Docker auf deinem vServer, startest deinen ersten Container, baust ein eigenes Image und betreibst einen kleinen Stack mit Compose. Ob Docker bei uns ohne Einschränkungen läuft, beantwortet Kann ich Docker auf meinem vServer benutzen?

Die Befehle gehen davon aus, dass du per SSH als root auf einem Debian– oder Ubuntu-Server angemeldet bist. Arbeitest du mit einem normalen Benutzer, setze sudo davor.

Die Grundbegriffe

BegriffWas es istWichtigster Befehl
ImageUnveränderliche Vorlage mit Programm, Bibliotheken und Standardkonfigurationdocker pull, docker images
ContainerLaufende Instanz eines Images mit eigenem Dateisystem, Netzwerk und eigener Prozesslistedocker run, docker ps
VolumeVon Docker verwalteter Speicher, der Container überlebtdocker volume ls
NetzwerkVirtuelles Netz, in dem sich Container über ihren Namen findendocker network ls
Compose-ProjektMehrere Container, Volumes und Netze, beschrieben in einer Dateidocker compose up -d

Aus einem Image startest du beliebig viele Container, und jeder läuft auf jedem Linux-Server gleich. Ein Container ist zum Wegwerfen gedacht: Löschst du ihn, ist alles weg, was nur im Container lag. Wichtige Daten gehören deshalb in ein Volume.

Docker installieren

Installiere Docker aus der offiziellen Paketquelle von Docker. Die Pakete docker.io aus den Debian- oder Ubuntu-Quellen sind oft deutlich älter und bringen Compose nicht als aktuelles Plugin mit. Wir empfehlen Debian 13 oder 12 sowie Ubuntu 24.04 oder 22.04.

Schritt 1: System aktualisieren und alte Pakete entfernen

apt update && apt upgrade -y
apt remove $(dpkg --get-selections docker.io docker-compose docker-doc docker-buildx podman-docker containerd runc | cut -f1)

Auf einem frischen Server ist meist nichts zu entfernen, der Befehl schadet aber nicht.

Schritt 2: Docker-Paketquelle einrichten

Für Debian:

apt install -y ca-certificates curl
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc

tee /etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/debian
Suites: $(. /etc/os-release && echo "$VERSION_CODENAME")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF

apt update

Für Ubuntu ersetzt du in der GPG-Adresse und in der Zeile URIs jeweils debian durch ubuntu:

apt install -y ca-certificates curl
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc

tee /etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF

apt update

Schritt 3: Docker Engine und Compose installieren

apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Docker startet danach automatisch und ist für den Systemstart aktiviert. Compose ist als Plugin dabei, der Befehl lautet docker compose mit Leerzeichen. Das alte docker-compose mit Bindestrich ist veraltet.

Schritt 4: Installation prüfen

systemctl status docker
docker run --rm hello-world
docker compose version

Gibt hello-world eine Begrüßung aus, ist Docker einsatzbereit. Updates kommen ab jetzt mit apt update && apt upgrade, siehe Mein Linux Server hat Updates! Was jetzt?

Optional: Docker ohne root nutzen

usermod -aG docker deinbenutzer

Danach meldest du dich neu an. Wichtig: Wer in der Gruppe docker ist, hat praktisch root-Rechte auf dem Server. Nimm dort nur Benutzer auf, denen du auch root geben würdest.

Der erste Container mit docker run

Starte einen Webserver aus dem offiziellen nginx-Image:

docker run -d --name web -p 8080:80 nginx:stable
  • -d startet den Container im Hintergrund.
  • --name web gibt ihm einen Namen, mit dem du ihn später ansprichst.
  • -p 8080:80 leitet Port 8080 deines Servers auf Port 80 im Container.
  • nginx:stable ist das Image samt Tag. Fehlt es lokal, lädt Docker es automatisch herunter.

Danach erreichst du den Webserver unter http://62.68.75.69:8080, wobei du die IP durch die deines Servers ersetzt. Mit diesen Befehlen verwaltest du den Container:

docker ps # laufende Container
docker ps -a # alle Container, auch gestoppte
docker logs -f web # Logs live verfolgen
docker exec -it web sh # Shell im Container öffnen
docker stop web # Container stoppen
docker start web # Container wieder starten
docker rm -f web # Container stoppen und löschen

Mit --restart unless-stopped startet ein Container nach einem Neustart des vServers automatisch wieder. Einen eigenen systemd-Dienst brauchst du dafür nicht. Mit --rm löscht Docker den Container, sobald er endet, praktisch für Tests.

Images und Tags

Ein Image heißt name:tag. Der Tag bezeichnet die Version, zum Beispiel mariadb:11 oder postgres:17. Lässt du ihn weg, nimmt Docker latest. Das ist bequem, kann aber beim nächsten pull eine neue Hauptversion bringen. Bei Datenbanken legst du deshalb eine feste Version fest.

docker pull nginx:stable # Image laden oder aktualisieren
docker images # lokale Images anzeigen
docker rmi nginx:stable # Image löschen (nur, wenn kein Container es nutzt)

Ein eigenes Image mit einem Dockerfile

Ein Dockerfile beschreibt, wie ein Image entsteht. Im Beispiel packst du eine statische Webseite in nginx. Lege einen Projektordner mit einer Seite an:

mkdir -p /opt/meine-seite/site && cd /opt/meine-seite
echo '<h1>Hallo aus meinem Container</h1>' > site/index.html
nano Dockerfile
FROM nginx:stable-alpine
COPY site/ /usr/share/nginx/html/
  • FROM wählt das Basis-Image.
  • COPY kopiert Dateien aus deinem Ordner ins Image.
  • RUN führt beim Bauen einen Befehl aus, zum Beispiel RUN apk add --no-cache curl.
  • CMD legt den Startbefehl fest, falls das Basis-Image keinen mitbringt.
docker build -t meine-seite:1.0 .
docker run -d --name seite -p 8081:80 meine-seite:1.0

Der Punkt am Ende von docker build steht für den aktuellen Ordner. Eine Datei .dockerignore schließt Dateien vom Kopieren aus, zum Beispiel .git oder .env. Das hält das Image klein und verhindert, dass Geheimnisse darin landen.

Daten dauerhaft speichern

Datenbanken, Uploads und Konfigurationen gehören in ein Volume oder einen Bind Mount.

Volumes: von Docker verwaltet

Volumes legt Docker selbst unter /var/lib/docker/volumes/ an. Das ist der empfohlene Weg für Datenbanken und Anwendungsdaten.

docker volume create webdaten
docker run -d --name web2 --restart unless-stopped \
 -v webdaten:/usr/share/nginx/html \
 -p 8082:80 nginx:stable

docker volume ls
docker volume inspect webdaten

Bind Mounts: ein Ordner deines Servers

Mit einem Bind Mount bindest du einen Ordner deines vServers in den Container ein, praktisch für Konfigurationsdateien, die du direkt bearbeiten willst. Mit :ro darf der Container nur lesen.

mkdir -p /srv/web
docker run -d --name web3 --restart unless-stopped \
 -v /srv/web:/usr/share/nginx/html:ro \
 -p 8083:80 nginx:stable

Volumes und Bind Mounts read-only einbinden

Muss ein Container Daten nur lesen, bindest du sie schreibgeschützt ein. Das gilt für Konfigurationsdateien, statische Webseiten und geteilte Daten. Selbst wenn die Anwendung im Container Fehler hat oder übernommen wird, kann sie diese Dateien nicht verändern. Du hängst dazu :ro (read-only) an den Pfad im Container. Das funktioniert bei Volumes und bei Bind Mounts:

# Volume read-only
docker run -d --name web4 -v webdaten:/usr/share/nginx/html:ro -p 8084:80 nginx:stable

# Bind Mount read-only (Konfigurationsdatei)
docker run -d --name web5 -v /srv/nginx.conf:/etc/nginx/nginx.conf:ro -p 8085:80 nginx:stable

In Compose schreibst du dasselbe in die Kurzform oder in die ausführliche Form mit read_only: true:

services:
 web:
 image: nginx:stable
 volumes:
 - ./site:/usr/share/nginx/html:ro
 - type: volume
 source: webdaten
 target: /daten
 read_only: true

volumes:
 webdaten:

Ein Volume, in das ein anderer Container schreibt und das du in einem zweiten Container nur liest, ist ein typischer Einsatz. So kann zum Beispiel ein Webserver die hochgeladenen Dateien eines anderen Dienstes ausliefern, aber nicht verändern.

Mit read_only: true auf Dienstebene machst du das gesamte Dateisystem des Containers schreibgeschützt. Schreibt die Anwendung temporäre Dateien, gibst du dafür einen flüchtigen Speicher frei:

services:
 web:
 image: nginx:stable
 read_only: true
 tmpfs:
 - /tmp
 - /var/cache/nginx
 - /var/run
 volumes:
 - ./site:/usr/share/nginx/html:ro

Viele Images schreiben beim Start in eigene Ordner. Startet ein Container mit read_only: true nicht, steht der betroffene Pfad in docker logs. Diesen Pfad gibst du dann als tmpfs oder als beschreibbares Volume frei.

Volumes sichern

Unsere kostenlosen Backups sichern die komplette Festplatte deines vServers, also auch alle Volumes. Sie laufen im laufenden Betrieb. Bei Datenbanken legst du deshalb zusätzlich einen Dump an. Ein einzelnes Volume packst du so in ein Archiv:

mkdir -p /root/backup
docker run --rm -v webdaten:/daten:ro -v /root/backup:/backup alpine \
 tar czf /backup/webdaten.tar.gz -C /daten .

Vor größeren Updates lohnt sich ein Snapshot im Cloud Manager. Snapshots werden nach 48 Stunden automatisch gelöscht und ersetzen kein Backup.

Docker Compose

Sobald eine Anwendung aus mehreren Containern besteht, wird docker run unübersichtlich. Mit Docker Compose beschreibst du alle Container, Netzwerke und Volumes in einer Datei und startest alles mit einem Befehl. Die Datei heißt compose.yaml. Der früher übliche Schlüssel version: am Anfang ist überflüssig.

Aufbau einer compose.yaml

  • services: enthält die Container. Jeder Eintrag ist ein Dienst mit eigenem Namen.
  • image: nennt das Image, alternativ baut build: eins aus einem Dockerfile.
  • ports: veröffentlicht Ports, environment: setzt Umgebungsvariablen.
  • volumes: bindet Speicher ein. Benannte Volumes deklarierst du zusätzlich ganz unten.
  • restart: legt fest, ob der Container nach Neustart oder Absturz wieder startet.
  • depends_on: bestimmt die Startreihenfolge.

Beispiel: Adminer mit PostgreSQL

Der Stack besteht aus einer PostgreSQL-Datenbank und Adminer als Weboberfläche. Der Healthcheck sorgt dafür, dass Adminer erst startet, wenn die Datenbank bereit ist.

mkdir -p /opt/adminer && cd /opt/adminer
echo "DB_PASSWORD=$(openssl rand -base64 24)" > .env
chmod 600 .env
nano compose.yaml
services:
 db:
 image: postgres:17
 restart: unless-stopped
 environment:
 POSTGRES_USER: demo
 POSTGRES_PASSWORD: ${DB_PASSWORD}
 POSTGRES_DB: demo
 volumes:
 - dbdaten:/var/lib/postgresql/data
 healthcheck:
 test: ["CMD-SHELL", "pg_isready -U demo -d demo"]
 interval: 5s
 timeout: 3s
 retries: 10

 adminer:
 image: adminer:latest
 restart: unless-stopped
 depends_on:
 db:
 condition: service_healthy
 ports:
 - "127.0.0.1:8080:8080"

volumes:
 dbdaten:

Compose liest die Datei .env im selben Ordner automatisch, das Passwort steht also nicht in der compose.yaml. Starte den Stack und prüfe ihn:

docker compose up -d
docker compose ps

Adminer lauscht nur auf 127.0.0.1, ist also nicht aus dem Internet erreichbar. Per SSH-Tunnel öffnest du die Oberfläche auf deinem Rechner:

ssh -L 8080:127.0.0.1:8080 root@62.68.75.69

Danach rufst du http://localhost:8080 im Browser auf. Als Server trägst du db ein, denn Container im selben Compose-Projekt erreichen sich über den Dienstnamen. Dafür brauchst du keinen veröffentlichten Port.

Die wichtigsten Compose-Befehle

Alle Befehle führst du im Projektordner aus:

docker compose up -d # alles starten (im Hintergrund)
docker compose ps # Status der Container
docker compose logs -f # Logs live verfolgen
docker compose config # Datei prüfen, Variablen eingesetzt anzeigen
docker compose pull && docker compose up -d # Images aktualisieren
docker compose restart db # einen Dienst neu starten
docker compose exec db bash # Shell im Container db
docker compose down # stoppen und Container entfernen (Volumes bleiben)

Ein Beispiel aus der Praxis mit Compose, Datenbank und Reverse Proxy ist unser n8n Installations-Guide.

Docker Hub und andere Registries

Der Docker Hub ist die zentrale Registry, aus der Docker standardmäßig Images lädt. Schreibst du image: nginx:stable, holt Docker das Image von dort.

  • Vertrauenswürdige Images wählen: Achte auf die Kennzeichnung „Docker Official Image“ oder „Verified Publisher“. Bei Images unbekannter Herkunft läuft fremder Code auf deinem Server.
  • Suchen und laden: docker search nginx und docker pull nginx:stable.
  • Andere Registries: Schreibe den Registry-Namen vor das Image, zum Beispiel ghcr.io/benutzer/projekt:1.0 oder quay.io/....

Download-Limit ohne Docker-Konto

Ohne Docker-Konto ist der Download von Images begrenzt. Der Docker Hub erlaubt anonym 100 Pulls in 6 Stunden pro IPv4-Adresse beziehungsweise pro IPv6-/64-Netz. Mit einem kostenlosen Docker-Konto sind es 200 Pulls in 6 Stunden. Das Limit zählt pro Adresse deines Servers, nicht pro Image und nicht pro Container. Jede Aktualisierung mit docker compose pull verbraucht Pulls, bei vielen Diensten kommen schnell einige zusammen.

Hast du das Limit erreicht, bricht der Download mit dem Fehler You have reached your pull rate limit ab (HTTP-Status 429). Dann wartest du, bis das Limit wieder sinkt, oder meldest dich an:

docker login

Statt deines Passworts verwendest du am besten ein Access Token, das du im Docker-Konto unter den Sicherheitseinstellungen erzeugst und jederzeit wieder löschen kannst. Das gilt besonders für CI-Pipelines und Server, die häufig Images ziehen. Das Limit gilt nur für Docker Hub, bei anderen Registries wie ghcr.io gelten deren eigene Regeln.

Häufige Stolpersteine

Veröffentlichte Ports umgehen die Firewall

Mit -p 8080:80 oder ports: trägt Docker eigene Paketfilter-Regeln ein. Diese greifen vor ufw oder firewalld. Ein Port, den du in ufw gesperrt hast, ist trotzdem aus dem Internet erreichbar, wenn ein Container ihn veröffentlicht. Dienste, die nur intern erreichbar sein sollen, bindest du deshalb an localhost:

ports:
 - "127.0.0.1:8080:80"

Mehr zur Absicherung allgemein steht in Linux Server absichern. Bei uns sind auf Linux-Servern zudem alle Ports standardmäßig offen, siehe Sind alle Ports freigegeben?

Container startet und beendet sich sofort

Der Grund steht fast immer im Log: docker logs web beziehungsweise docker compose logs db. Typische Ursachen sind fehlende Umgebungsvariablen, ein belegter Port oder Schreibrechte auf einem Bind Mount.

Port ist schon belegt

Die Meldung port is already allocated heißt, dass ein anderer Container oder Dienst den Port nutzt. ss -tlnp zeigt, wer ihn belegt. Wähle einen anderen Host-Port, zum Beispiel 8090:80.

Datenbank-Passwort ändert sich nicht

Datenbank-Images lesen Variablen wie POSTGRES_PASSWORD nur beim ersten Start mit leerem Volume. Änderst du das Passwort später in der .env, bleibt das alte im Volume. Setze es dann in der Datenbank selbst neu.

Volumes verschwinden

docker compose down -v und docker system prune --volumes löschen Volumes samt Inhalt. Lass -v weg, wenn du die Daten behalten willst.

Festplatte läuft voll

Alte Images, gestoppte Container und Build-Cache sammeln sich an:

docker system df
docker image prune
docker system prune

Container-Logs wachsen ohne Limit. Du begrenzt sie global in der Datei /etc/docker/daemon.json. Diese Datei existiert bei einer Standardinstallation nicht, du legst sie selbst an:

nano /etc/docker/daemon.json

Trage diesen Inhalt ein. Gibt es die Datei auf deinem Server schon, ergänze die beiden Einträge log-driver und log-opts in der vorhandenen Datei, statt sie zu überschreiben. Danach startest du Docker neu. Die Einstellung gilt nur für neu erstellte Container, bestehende erstellst du neu (bei Compose mit docker compose up -d --force-recreate):

{
 "log-driver": "json-file",
 "log-opts": {
 "max-size": "10m",
 "max-file": "3"
 }
}
systemctl restart docker

Cannot connect to the Docker daemon

Entweder läuft der Dienst nicht (systemctl start docker, Fehler in journalctl -u docker) oder dein Benutzer ist nicht in der Gruppe docker. Nach usermod musst du dich einmal ab- und wieder anmelden.

Verwandte Artikel