DarkWolfCave
docker

Docker Volumes — Wo liegen meine Daten wirklich?

Wolf untersucht mit Lupe verschiedene Docker-Container und findet versteckte Datenverzeichnisse
DarkWolf Maskottchen KI-Bild Generiert mit Gemini

Docker Volumes — wo liegen meine Daten wirklich?

Ich habe 9 PostgreSQL-Datenbanken auf 4 Servern migriert und dabei festgestellt: docker volume ls zeigt 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:

TypWer verwaltet es?Wo auf dem Host?Überlebt down -v?
Anonymes VolumeDocker (automatisch)/var/lib/docker/volumes/<hash>/Nein
Named VolumeDocker (du gibst den Namen)/var/lib/docker/volumes/<name>/Nein
Bind MountDuWo 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.

VIP Support
Wolf Support Avatar

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 down ohne -v kann gefährlich werden — wenn danach ein docker volume prune folgt, 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.

SituationEmpfehlung
Datenbanken, User-UploadsBind Mount
Docker Desktop Entwicklung (Mac/Windows)Named Volume
Swarm/K8s mit automatischem StorageNamed Volume + Driver
collectstatic, Build-CachesNamed Volume
Einzelner Linux-Server, persistente DatenBind 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 (chown für den Container-User)

Für bestehende Projekte:

  • docker inspect auf 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

Werbung

Backup & Storage Hardware

Bild Produkt Preis
Produktdaten werden geladen...
Letzte Aktualisierung: - | Infos zu Affiliate Links | Bilder von der Amazon Product Advertising API

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.

FAQ - Frequently Asked Questions DarkWolfCave
DarkWolf hilft bei FAQs

Häufig gestellte Fragen

Was ist der Unterschied zwischen anonymen und Named Volumes?
Named Volumes haben einen lesbaren Namen (z.B. postgres_data) und werden in der volumes:-Sektion der Compose-Datei deklariert. Anonyme Volumes haben einen 64-Zeichen-Hash als Namen und entstehen automatisch durch VOLUME-Anweisungen im Dockerfile. Beide werden von Docker unter /var/lib/docker/volumes/ verwaltet.
Warum zeigt docker volume ls nicht alle Volumes die mein Container nutzt?
Weil docker volume ls nur Named und anonyme Volumes zeigt — keine Bind Mounts. Außerdem siehst du nicht welches Volume zu welchem Container gehört. Nutze docker inspect CONTAINER um alle Mounts eines Containers zu sehen.
Wie finde ich heraus welches Volume zu welchem Container gehört?
Mit docker inspect CONTAINER --format '{{range .Mounts}}{{.Type}}: {{.Source}} → {{.Destination}}{{println}}{{end}}' siehst du alle Mounts eines Containers mit Typ, Quelle und Ziel.
Überleben Bind Mounts docker compose down -v?
Ja. docker compose down -v löscht Named und anonyme Volumes, aber Bind Mounts bleiben unberührt. Die Daten liegen in einem normalen Verzeichnis auf deinem Host und können nur durch explizites Löschen (z.B. rm -rf) verloren gehen.
Was passiert mit anonymen Volumes bei Container-Neuerstellung?
Bei docker compose down und erneutem up wird ein neues anonymes Volume zugewiesen. Das alte Volume verwaist — die Daten sind noch da, aber der Container nutzt sie nicht mehr. Das führt zu schleichendem Datenverlust.
Wie migriere ich von Named Volumes auf Bind Mounts?
Container stoppen, Daten aus dem Named Volume kopieren (sudo cp -a /var/lib/docker/volumes/VOLUME_NAME/_data/ ./data/), docker-compose.yml auf Bind Mount umstellen, Container starten und Daten prüfen. Danach das alte Volume mit docker volume rm löschen.

Kommentare

URLs werden automatisch verlinkt
Kommentare werden geladen...