Docker Volumes — wo liegen meine Daten wirklich?
Ich habe 9 PostgreSQL-Datenbanken auf 4 Servern migriert und dabei festgestellt:
docker volume lszeigt dir nicht die ganze Wahrheit. Anonyme Volumes, Named Volumes, Bind Mounts — Docker hat drei verschiedene Wege deine Daten zu speichern, und nicht alle sind gleich sicher.DarkWolfCave.de
Drei Wege, ein Problem
Docker speichert persistente Daten auf drei Arten. Das klingt simpel — die Unterschiede sind im Alltag aber nicht offensichtlich, und das macht sie im Produktivbetrieb gefährlich.
Hier die Kurzversion:
| Typ | Wer verwaltet es? | Wo auf dem Host? | Überlebt down -v? |
|---|---|---|---|
| Anonymes Volume | Docker (automatisch) | /var/lib/docker/volumes/<hash>/ | Nein |
| Named Volume | Docker (du gibst den Namen) | /var/lib/docker/volumes/<name>/ | Nein |
| Bind Mount | Du | Wo du willst (z.B. ./data/) | Ja |
Die erste Überraschung für viele: Named Volumes und anonyme Volumes sind beim Löschen gleich gefährdet. Nur Bind Mounts geben dir die volle Kontrolle.
Du wirst hier einen groben Überblick finden.
Allerdings biete ich dir auch noch etwas mehr Support an:
- Du benötigst persönlichen Support
- Du möchtest von Beginn an Unterstützung bei deinem Projekt
- Du möchtest ein hier vorgestelltes Plugin durch mich installieren und einrichten lassen
- Du würdest gerne ein von mir erstelltes Script etwas mehr an deine Bedürfnisse anpassen
Für diese Punkte und noch einiges mehr habe ich einen limitierten VIP-Tarif eingerichtet.
Falls der Tarif gerade nicht verfügbar ist, kontaktiere mich auf Discord!
Anonyme Volumes — die unsichtbare Gefahr
Anonyme Volumes sind der Teil den die meisten Docker-Nutzer nicht kennen. Sie entstehen nicht durch deine docker-compose.yml, sondern durch das Docker-Image selbst.
Viele offizielle Images enthalten im Dockerfile eine VOLUME-Anweisung:
# Aus dem offiziellen PostgreSQL Dockerfile
VOLUME /var/lib/postgresql
Diese Anweisung sagt Docker: “Erstelle automatisch ein Volume für dieses Verzeichnis, falls der Nutzer keines konfiguriert.” Das klingt hilfreich — ist aber eine Falle, wenn du ein Volume an einer anderen Stelle mountest.
Zwei Mounts statt einem
Wenn deine docker-compose.yml ein Named Volume auf /var/lib/postgresql/data mountet, hat dein Container zwei Mounts:
/var/lib/postgresql/ ← Anonymes Volume (vom Dockerfile)
/var/lib/postgresql/data/ ← Dein Named Volume (aus der Compose-Datei)
Das siehst du nur mit docker inspect:
docker inspect mein_postgres --format '{{json .Mounts}}' | python3 -m json.tool
Die Ausgabe zeigt dann so etwas:
[
{
"Type": "volume",
"Name": "a3f7b2c9e8d1...",
"Destination": "/var/lib/postgresql"
},
{
"Type": "volume",
"Name": "meinprojekt_pgdata",
"Destination": "/var/lib/postgresql/data"
}
]
Das erste Volume mit dem Hash-Namen — das ist das anonyme Volume. docker volume ls zeigt es zwar an, aber du siehst nicht auf Anhieb dass es zu deinem PostgreSQL-Container gehört.
Warum das zum Problem wird
Wenn die Anwendung im Container ihre Daten nicht in dein konfiguriertes Volume schreibt, sondern in das Elternverzeichnis, landen sie im anonymen Volume. Genau das passiert bei PostgreSQL 18 mit dem geänderten PGDATA-Pfad.
Bei jedem docker compose down und up bekommt der Container ein neues anonymes Volume zugewiesen. Das alte verwaist — die Daten sind noch drin, aber der Container nutzt sie nicht mehr.
Auf einem meiner Server hatte sich das über Wochen aufgebaut: 21 verwaiste anonyme Volumes, alle von PostgreSQL-Neustarts.
Named Volumes — sichtbar, aber verletzlich
Named Volumes sind der Standard in den meisten Docker-Tutorials:
services:
postgres:
image: postgres:18-alpine
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
Du gibst dem Volume einen Namen, Docker verwaltet den Rest. Die Daten liegen unter /var/lib/docker/volumes/meinprojekt_postgres_data/_data/. Soweit so gut.
Das Problem: Named Volumes werden von Docker als “zum Projekt gehörend” betrachtet. Und Docker stellt Befehle bereit die sie löschen — ohne Rückfrage:
# Alle drei löschen dein Named Volume:
docker compose down -v # löscht Projekt-Volumes
docker volume prune # löscht "unbenutzte" Volumes
docker system prune --volumes # löscht alles Unbenutzte
Keiner dieser Befehle fragt nach. Kein Papierkorb, kein Undo.
⚠️ Wichtig: Auch
docker compose downohne-vkann gefährlich werden — wenn danach eindocker volume prunefolgt, sind die Volumes “unbenutzt” und werden gelöscht.
Was du auf dem Host siehst (und was nicht)
Ein Named Volume liegt unter /var/lib/docker/volumes/. Du brauchst Root-Rechte um reinzuschauen:
# Inhalt eines Named Volume anzeigen
sudo ls /var/lib/docker/volumes/meinprojekt_postgres_data/_data/
Das Verzeichnis gehört Docker. Es taucht nicht in deinen normalen Backup-Scripts auf. Wenn du /home oder /opt sicherst, fehlen die Docker-Volumes.
Du willst Docker nicht selbst einrichten? Ich übernehme das für dich — schau dir meine Services an
Bind Mounts — du bestimmst wo die Daten liegen
Bei einem Bind Mount sagst du Docker: “Nimm dieses Verzeichnis von meinem Host und häng es in den Container ein.”
services:
postgres:
image: postgres:18-alpine
volumes:
- ./data/postgres:/var/lib/postgresql
Die Daten liegen jetzt unter ./data/postgres/ — direkt im Projektverzeichnis, sichtbar mit ls, sicherbar mit jedem Backup-Tool.
Was Bind Mounts besser machen
# Du siehst die Daten direkt
ls -la ./data/postgres/18/docker/
# Du sicherst sie mit Standard-Tools
rsync -avz ./data/postgres/ /backup/postgres/
# docker compose down -v löscht sie NICHT
docker compose down -v # Bind Mount bleibt!
# docker volume prune löscht sie NICHT
docker volume prune # Bind Mount bleibt!
# Nur das löscht sie:
rm -rf ./data/postgres/ # Und das machst du nicht ausversehen
Der einzige Nachteil
Bei Named Volumes kopiert Docker beim ersten Start den Inhalt aus dem Image ins Volume — inklusive korrekter Dateirechte. Bei Bind Mounts passiert das nicht. Du musst die Rechte selbst setzen:
# PostgreSQL Alpine Image nutzt uid 70
sudo chown -R 70:70 ./data/postgres/
Das ist ein einmaliger Schritt, der sich lohnt.
Wann Named Volumes trotzdem sinnvoll sind
Bind Mounts sind meine Empfehlung für persistente Daten auf Linux-Servern. Aber Named Volumes haben Situationen in denen sie die bessere Wahl sind:
Docker Desktop (macOS/Windows): Bind Mounts sind auf macOS und Windows deutlich langsamer als Named Volumes — bis zu 3,5x bei npm install. Der Grund: Jeder Dateizugriff muss über die VM-Boundary synchronisiert werden. Named Volumes leben direkt im Filesystem der Linux-VM und umgehen dieses Problem. Auf Linux-Servern gibt es keinen Unterschied.
Docker Swarm / Kubernetes: Named Volumes unterstützen Volume Drivers — Docker kann damit NFS-Shares, Cloud-Storage oder andere Backends direkt einbinden, ohne dass der Host sie vorher gemountet haben muss. Das ist relevant, wenn Container auf verschiedenen Hosts starten und Storage automatisch provisioniert werden soll. Auf einem einzelnen Server mit vorhandenem NFS-Mount (z.B. per autofs) bringt das keinen Vorteil — ein Bind Mount auf das gemountete Verzeichnis funktioniert genauso.
Erstbefüllung aus dem Image: Wenn du ein Named Volume zum ersten Mal an einen Container hängst, kopiert Docker den Inhalt aus dem Image ins Volume — inklusive Dateirechte. Bei Bind Mounts passiert das nicht. Ein leeres Host-Verzeichnis überschreibt den Container-Inhalt. In der Praxis ist das selten ein Problem (ein chown beim Setup reicht), aber bei Images mit komplexer Verzeichnisstruktur kann es Arbeit sparen.
Regenerierbare Daten: Für Daten die bei jedem Deploy neu erstellt werden — collectstatic-Output, Build-Caches, temporäre Dateien — sind Named Volumes eine gute Wahl. Hier ist es kein Problem wenn sie bei down -v gelöscht werden.
| Situation | Empfehlung |
|---|---|
| Datenbanken, User-Uploads | Bind Mount |
| Docker Desktop Entwicklung (Mac/Windows) | Named Volume |
| Swarm/K8s mit automatischem Storage | Named Volume + Driver |
| collectstatic, Build-Caches | Named Volume |
| Einzelner Linux-Server, persistente Daten | Bind Mount |
docker inspect — das Debugging-Werkzeug
Wenn du wissen willst was wirklich in deinem Container passiert, ist docker inspect dein wichtigstes Tool. Nicht docker volume ls — das zeigt dir nur eine Liste ohne Zuordnung.
Alle Mounts eines Containers anzeigen
docker inspect CONTAINER --format \
'{{range .Mounts}}Type={{.Type}} Source={{.Source}} → {{.Destination}}{{println}}{{end}}'
Beispiel-Ausgabe eines PostgreSQL-Containers mit Named Volume:
Type=volume Source=/var/lib/docker/volumes/hash123/_data → /var/lib/postgresql
Type=volume Source=/var/lib/docker/volumes/pgdata/_data → /var/lib/postgresql/data
Zwei Mounts! Der erste ist das anonyme Volume — unsichtbar in der docker-compose.yml, aber trotzdem da.
Nach der Migration auf Bind Mount:
Type=bind Source=/opt/apps/meinprojekt/data/postgres → /var/lib/postgresql
Ein einziger Mount. Keine Überraschungen.
Prüfen wo die Daten wirklich liegen
Wenn du den Verdacht hast dass deine Daten nicht dort sind wo du denkst:
# PGDATA im Container prüfen
docker exec mein_postgres printenv PGDATA
# Datenverzeichnis im Container anzeigen
docker exec mein_postgres ls -la /var/lib/postgresql/
# Prüfen ob Daten im Named Volume oder im Anonymen liegen
sudo ls /var/lib/docker/volumes/VOLUME_NAME/_data/
Wenn das Named Volume leer ist und die Daten im anonymen Volume liegen, hast du genau das Problem das ich in meinem PostgreSQL-Artikel beschrieben habe.
Von Named Volume auf Bind Mount umstellen
Falls du bestehende Container umstellen willst — hier der Ablauf den ich für 9 Projekte auf 4 Servern verwendet habe:
# 1. Backup erstellen (Container muss noch laufen!)
docker exec mein_postgres pg_dump -U DEIN_USER -d DEINE_DB \
--format=custom > backup_$(date +%Y%m%d).dump
# 2. Prüfen wo die Daten im Volume liegen
sudo ls /var/lib/docker/volumes/VOLUME_NAME/_data/
# Daten direkt drin (base, global, pg_wal)? → Pfad A
# Unterverzeichnis "data/" mit den Daten? → Pfad B
# 3. Container stoppen (NICHT down -v!)
docker compose stop postgres
# 4. Verzeichnis erstellen und Daten kopieren
mkdir -p ./data/postgres/18
# Pfad A: Daten direkt im Volume
sudo cp -a /var/lib/docker/volumes/VOLUME_NAME/_data \
./data/postgres/18/docker
# Pfad B: Daten in data/ Unterverzeichnis
sudo cp -a /var/lib/docker/volumes/VOLUME_NAME/_data/data \
./data/postgres/18/docker
# 5. Rechte setzen (uid 70 = postgres in Alpine)
sudo chown -R 70:70 ./data/postgres/
# 6. docker-compose.yml anpassen
# volumes:
# - ./data/postgres:/var/lib/postgresql
# Named Volume aus volumes:-Sektion entfernen
# 7. Container starten und prüfen
docker compose up -d postgres
docker exec mein_postgres psql -U DEIN_USER -d DEINE_DB \
-c "SELECT count(*) FROM DEINE_TABELLE;"
# 8. Altes Named Volume löschen (erst nach Verifikation!)
docker volume rm VOLUME_NAME
💡 Tipp: Bei Schritt 2 ist es wichtig die tatsächliche Verzeichnisstruktur im Volume zu prüfen. Je nachdem ob ein PGDATA-Override gesetzt war, liegen die Daten direkt im Volume-Root oder in einem Unterverzeichnis.
Verwaiste Volumes finden und aufräumen
Nach einer Migration bleiben alte Volumes zurück. So findest du sie:
# Alle Volumes anzeigen
docker volume ls
# Verwaiste Volumes finden (von keinem Container genutzt)
docker volume ls -f dangling=true
Aber nicht blind löschen! Erst prüfen was drin ist:
# Inhalt eines anonymen Volume inspizieren
docker run --rm -v VOLUME_HASH:/data alpine ls -la /data/
# Wenn PostgreSQL-Daten drin sind: Größe prüfen
docker run --rm -v VOLUME_HASH:/data alpine du -sh /data/
Wenn du sicher bist dass die Daten bereits im Bind Mount liegen und der Container damit läuft, kannst du das alte Volume löschen:
docker volume rm VOLUME_NAME
⚠️ Wichtig: Lösche nie mehrere Volumes auf einmal mit
docker volume prune. Prüfe jedes Volume einzeln — eines davon könnte noch von einem gestoppten Container gebraucht werden.
Du willst Docker nicht selbst einrichten? Ich übernehme das für dich — schau dir meine Services an
Meine Checkliste für Docker Volumes
Nach der Migration aller meiner Projekte habe ich diese Regeln:
Für neue Projekte:
- Bind Mounts für alle nicht-regenerierbaren Daten (Datenbanken, Uploads)
- Named Volumes nur für regenerierbare Daten (Build-Caches, collectstatic)
-
data/in.gitignore - Rechte prüfen (
chownfür den Container-User)
Für bestehende Projekte:
-
docker inspectauf alle Container mit persistenten Daten - Anonyme Volumes identifizieren — gibt es versteckte Mounts?
- Bei Datenbank-Containern: Liegen die Daten wo du denkst?
- Migration auf Bind Mounts planen (Backup → Kopie → Umstellung → Verifikation)
Regelmäßig:
-
docker volume ls -f dangling=true— verwaiste Volumes aufräumen - Bind-Mount-Verzeichnisse in Backup-Lösung einschließen
Hardware für Docker-Backups und Datensicherung
Backup & Storage Hardware
| Bild | Produkt | Preis | |
|---|---|---|---|
| Produktdaten werden geladen... | |||
Du betreibst Docker auf einem Raspberry Pi? Dann schau dir meine Artikel zu Docker auf dem Raspberry Pi installieren, Docker Backup und PostgreSQL in Docker — Datenverlust vermeiden an. Dort findest du die konkreten Konfigurationen und Backup-Scripts die ich selbst verwende.
Kommentare