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
| Begriff | Was es ist | Wichtigster Befehl |
|---|---|---|
| Image | Unveränderliche Vorlage mit Programm, Bibliotheken und Standardkonfiguration | docker pull, docker images |
| Container | Laufende Instanz eines Images mit eigenem Dateisystem, Netzwerk und eigener Prozessliste | docker run, docker ps |
| Volume | Von Docker verwalteter Speicher, der Container überlebt | docker volume ls |
| Netzwerk | Virtuelles Netz, in dem sich Container über ihren Namen finden | docker network ls |
| Compose-Projekt | Mehrere Container, Volumes und Netze, beschrieben in einer Datei | docker 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 -dstartet den Container im Hintergrund.--name webgibt ihm einen Namen, mit dem du ihn später ansprichst.-p 8080:80leitet Port 8080 deines Servers auf Port 80 im Container.nginx:stableist 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/ FROMwählt das Basis-Image.COPYkopiert Dateien aus deinem Ordner ins Image.RUNführt beim Bauen einen Befehl aus, zum BeispielRUN apk add --no-cache curl.CMDlegt 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 bautbuild: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 nginxunddocker pull nginx:stable. - Andere Registries: Schreibe den Registry-Namen vor das Image, zum Beispiel
ghcr.io/benutzer/projekt:1.0oderquay.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.