Docker-Netzwerke und Volumes richtig nutzen
Container finden sich nicht, Daten sind nach dem Neustart weg? Diese Anleitung zeigt user-defined Bridge-Netze mit DNS, Ports, Named Volumes und Bind-Mounts, Backup per tar und UID/GID-Fallen, getestet mit Docker 29.
Geprüft am 29.09.2026 · für docker-engine 29.1.3
WerbelinksMit * markierte Links sind Werbelinks: Bei einem Kauf erhalten wir eine Provision, der Preis bleibt gleich. Als Amazon-Partner verdiene ich an qualifizierten Verkäufen. Mehr dazu

Container, die sich nicht finden, und Daten, die nach docker rm fehlen, haben meist dieselbe Ursache: falsch genutzte Docker-Netzwerke und Volumes. Alle Befehle gelten für Debian 12 oder Ubuntu 24.04 LTS und sind mit Docker Engine 29 getestet.
Voraussetzungen
- Linux-Server mit Debian 12 oder Ubuntu 24.04 LTS und Docker Engine (siehe Docker installieren).
- Ein Benutzer in der Gruppe
dockeroder Zugriff persudo. - Docker Compose v2 (
docker compose) für Schritt 9. - 1 CPU-Kern, 1 GB freier RAM, rund 1 GB Speicher für die Beispiel-Images (
nginx,postgres,alpine,ubuntu), x86-64 oder ARM64.
Prüfen Sie vorab, ob Docker bereit ist:
docker version
docker info | grep -i 'Server Version'
Schritt 1: Default Bridge vs. user-defined Bridge verstehen
Jede frische Docker-Installation bringt drei Netzwerke mit:
docker network ls
NETWORK ID NAME DRIVER SCOPE
6f1a... bridge bridge local
9c2b... host host local
3d4e... none null local
Im Default-Bridge-Netz bridge (docker0) erreichen sich Container nur per IP-Adresse, ohne Namensauflösung (außer über das veraltete --link).
Ein selbst angelegtes user-defined Bridge-Netz bietet zusätzlich:
- DNS-Auflösung über Container-Namen und Aliase per eingebettetem DNS-Server (
127.0.0.11), externe Namen über das Host-DNS. - Isolation: Nur Container im selben Netz erreichen sich.
Docker empfiehlt je Anwendung ein eigenes Netz.
Verifizieren: docker network ls zeigt die Netze bridge, host und none.
Schritt 2: Ein eigenes Netzwerk anlegen und inspizieren
Ohne weitere Angabe nutzt Docker den Treiber bridge mit DNS:
docker network create my-net
Subnetz und Gateway legen Sie selbst fest, etwa gegen Überschneidung mit dem Firmennetz:
docker network create -d bridge \
--subnet=172.28.0.0/16 \
--gateway=172.28.0.1 \
br0
inspect zeigt Subnetz, Gateway und verbundene Container samt IPs:
docker network inspect my-net
Verwaltungsbefehle:
docker network ls # alle Netzwerke auflisten
docker network connect my-net mein-container # laufenden Container ins Netz haengen
docker network disconnect my-net mein-container # Container vom Netz trennen
docker network rm my-net # Netz loeschen (muss leer sein)
docker network prune # alle ungenutzten Netze entfernenVerifizieren: docker network inspect my-net --format '{{.Driver}} {{range .IPAM.Config}}{{.Subnet}}{{end}}' gibt bridge und ein Subnetz aus, z. B. bridge 172.20.0.0/16.
Schritt 3: DNS-Auflösung über Container-Namen testen
Starten Sie Webserver und Datenbank im selben Netz. Ohne POSTGRES_PASSWORD beendet sich das PostgreSQL-Image sofort:
# Webserver: Port 8080 (Host) -> 80 (Container) nach aussen oeffnen
docker run -d --name web --network my-net -p 8080:80 nginx:latest
# Datenbank: KEIN -p, nur intern erreichbar
docker run -d --name db --network my-net -e POSTGRES_PASSWORD=bitte-aendern postgres:18
Namensauflösung per Wegwerf-Container im selben Netz prüfen:
docker run --rm --network my-net alpine ping -c2 db
Verifizieren: Der Ping liefert zwei Antworten von einer Adresse aus dem Subnetz von my-net, und docker ps zeigt web und db mit Status „Up“. Gegentest: Ohne --network my-net (Default-Bridge-Netz) scheitert derselbe Aufruf mit ping: bad address 'db'.
Schritt 4: Ports veröffentlichen vs. interne Kommunikation
- -p / --publish macht einen Container-Port vom Host und potenziell aus dem Internet erreichbar:
-p 8080:80leitet Host-Port 8080 auf Container-Port 80. - Container im selben Netz brauchen untereinander kein
-p.EXPOSEdokumentiert den Port nur.
Vorsicht: -p umgeht die UFW-Firewall
-p 8080:80 bindet standardmäßig an alle Interfaces (0.0.0.0). Docker schreibt eigene iptables-Regeln, die vor UFW greifen: Der Dienst bleibt erreichbar, obwohl UFW ihn scheinbar blockt. Für einen Reverse Proxy auf demselben Host binden Sie an Loopback:
# Container web aus Schritt 3 ersetzen, nur auf dem Host erreichbar:
docker rm -f web
docker run -d --name web --network my-net -p 127.0.0.1:8080:80 nginx:latest
Verifizieren: curl -I http://127.0.0.1:8080 auf dem Server liefert HTTP/1.1 200 OK, von einem anderen Rechner ist Port 8080 nicht erreichbar. docker port web zeigt 80/tcp -> 127.0.0.1:8080.
Die TLS-Freigabe nach außen zeigt die Anleitung nginx Reverse Proxy mit TLS.
Schritt 5: host- und none-Modus gezielt einsetzen
Zwei weitere Modi wählen Sie per --network:
| Modus | Verhalten | Einsatz |
|---|---|---|
bridge (Default) | eigenes virtuelles Netz, Port-Mapping per -p | Standard für fast alles |
host | teilt den Netzwerk-Stack des Hosts direkt; -p wird ignoriert | nur wenn maximale Netzwerk-Performance nötig ist |
none | gar kein Netzwerk, vollständige Isolation | Batch-Jobs ohne Netzbedarf, Sicherheits-Sandbox |
# host-Modus: nutzt den Host-Netzwerk-Stack direkt, -p wird ignoriert
# Achtung: nginx belegt dann Port 80 des Hosts
docker run -d --network host nginx:latest
# none-Modus: vollstaendige Netzwerk-Isolation
docker run -d --network none alpine sleep 1000
Der host-Modus birgt Port-Konflikte und Sicherheitsrisiken; nur wo nötig einsetzen.
Netz ohne Internetzugang (--internal)
Ein Netz ohne Default-Gateway und Internet, etwa für eine Datenbankschicht, erzeugt --internal:
docker network create --internal isoliert-netVerifizieren: docker run --rm --network isoliert-net alpine wget -q -T3 -O- http://1.1.1.1 endet mit „Network unreachable“, docker run --rm --network none alpine ip addr zeigt nur das Loopback-Interface lo.
Schritt 6: Named Volumes vs. Bind-Mounts für persistente Daten
Änderungen im Container-Dateisystem gehen beim Löschen verloren. Für dauerhafte Daten gibt es zwei Wege:
| Aspekt | Named Volume | Bind-Mount |
|---|---|---|
| Verwaltung | vollständig von Docker (unter /var/lib/docker/volumes/NAME/_data) | konkreter Host-Pfad, von Ihnen verwaltet |
| Typischer Einsatz | Datenbank-Daten, Anwendungs-State | Config-Dateien, Quellcode in der Entwicklung |
| Vorbefüllung | leeres Volume wird beim ersten Mount mit dem Image-Inhalt des Zielpfads gefüllt | überdeckt vorhandene Container-Dateien (erscheint ggf. leer) |
Docker empfiehlt die ausführlichere --mount-Syntax, -v funktioniert weiterhin:
docker volume create my-vol
# klassisch mit -v
docker run -d --name app -v my-vol:/data nginx:latest
# bevorzugt mit --mount (expliziter)
docker run -d --name app2 --mount type=volume,src=my-vol,dst=/data nginx:latest
Ein Bind-Mount bindet einen absoluten Host-Pfad ein, :ro bzw. readonly verhindert Schreibzugriffe:
# Bind-Mount, read-only, mit -v
docker run -d --name app3 -v /srv/appdata:/data:ro nginx:latest
# Bind-Mount mit --mount (Quelle MUSS existieren)
docker run -d --name app4 --mount type=bind,src=/srv/appdata,dst=/data nginx:latest
Unterschiede der Syntaxen:
- Fehlende Quelle:
-vlegt das Host-Verzeichnis als root an,--mount type=bindbricht mit „bind source path does not exist“ ab. - SELinux-Labels (
z= geteilt,Z= privat) gehen nur mit-v. Relevant auf RHEL/Fedora; Debian/Ubuntu nutzen AppArmor.
Verwaltung wie bei Netzwerken:
docker volume ls # alle Volumes auflisten
docker volume inspect my-vol # Mountpoint und Details anzeigen
docker volume rm my-vol # einzelnes Volume loeschen
docker volume prune # ungenutzte Volumes entfernen (Vorsicht!)
Anonyme Volumes erhalten einen Zufallsnamen; docker run --rm und docker rm -v löschen sie mit, docker rm allein nicht. Named Volumes bleiben.
Verifizieren: docker volume inspect my-vol zeigt den Mountpoint /var/lib/docker/volumes/my-vol/_data. Die Vorbefüllung prüfen Sie mit einem frischen Volume: docker run --rm -v test-vol:/usr/share/nginx/html nginx:latest ls /usr/share/nginx/html zeigt 50x.html und index.html aus dem Image.
Schritt 7: Ein Volume sichern und wiederherstellen (tar)
Ein kurzlebiger Hilfscontainer hängt das Named Volume ein und schreibt es per tar ins aktuelle Verzeichnis:
# Named Volume direkt nach my-vol.tar.gz sichern (gzip)
docker run --rm \
-v my-vol:/data \
-v $(pwd):/backup \
alpine tar czf /backup/my-vol.tar.gz -C /data .
Die Methode der Docker-Doku übernimmt per --volumes-from die Volumes eines bestehenden Containers (hier dbstore, angelegt mit docker create -v /dbdata --name dbstore ubuntu /bin/bash):
# Sichern: Volumes von dbstore nach backup.tar im aktuellen Verzeichnis
docker run --rm --volumes-from dbstore \
-v $(pwd):/backup \
ubuntu tar cvf /backup/backup.tar /dbdata
# Wiederherstellen in einen Container dbstore2
docker run --rm --volumes-from dbstore2 \
-v $(pwd):/backup \
ubuntu bash -c "cd /dbdata && tar xvf /backup/backup.tar --strip 1"
Datenbanken vorher stoppen oder per Dump (pg_dump, mysqldump) sichern, sonst droht eine inkonsistente Kopie.
Verifizieren: tar tzf my-vol.tar.gz listet die Dateien des Volumes. Nach dem Restore zeigt docker run --rm --volumes-from dbstore2 ubuntu ls -R /dbdata dieselben Dateien wie im Original.
Schritt 8: Die UID/GID-Falle vermeiden
Häufigste Stolperfalle bei Bind-Mounts sind Besitzrechte: Container laufen standardmäßig als root (UID 0), Dateien im Bind-Mount gehören auch auf dem Host root. Läuft die Anwendung als anderer Benutzer, folgt Permission denied.
Drei saubere Lösungen, kein dauerhaftes chmod 777:
# 1) Ownership eines Named Volumes einmalig auf UID:GID 1000 setzen
docker run --rm -v my-vol:/data alpine chown -R 1000:1000 /data
# 2) Container mit der UID/GID des Host-Users starten,
# damit erzeugte Dateien dem Host-User gehoeren
docker run -u $(id -u):$(id -g) -v /srv/appdata:/data alpine touch /data/test.txt
Dritte Variante: Entrypoint-chown oder Init-Container setzt die Rechte beim Start, danach läuft die Anwendung ohne root.
Verifizieren: ls -ln /srv/appdata zeigt test.txt mit der UID/GID Ihres Benutzers (z. B. 1000 1000), eine ohne -u geschriebene Datei dagegen 0 0. docker run --rm -v my-vol:/data alpine stat -c '%u:%g' /data liefert nach Lösung 1 1000:1000.
Schritt 9: Netzwerke und Volumes in Docker Compose
Compose legt automatisch ein Netz PROJEKT_default an; Services erreichen sich über den Service-Namen. Ab PostgreSQL 18 gehört das Volume nach /var/lib/postgresql statt /var/lib/postgresql/data, sonst startet der Container nicht:
services:
web:
image: nginx:latest
ports:
- "127.0.0.1:8080:80" # nur lokal erreichbar
networks:
- frontend
- backend
depends_on:
- db
db:
image: postgres:18
environment:
POSTGRES_PASSWORD: bitte-aendern
volumes:
- db-data:/var/lib/postgresql # Named Volume, ab PostgreSQL 18 dieser Pfad
networks:
- backend # KEIN ports: -> db ist nur intern erreichbar
networks:
frontend:
backend:
internal: true # backend hat keinen Internetzugang
volumes:
db-data:
Starten und prüfen:
docker compose up -d
docker compose ps
docker compose exec web getent hosts db # DNS ueber den Service-Namen db
Das nginx-Image enthält kein ping, daher prüft getent hosts die Auflösung. external: true bindet vorhandene Netze oder Volumes ein.
Verifizieren: docker compose ps zeigt web und db mit „Up“, getent hosts db gibt eine IP und db aus, curl -I http://127.0.0.1:8080 liefert 200 OK, docker compose exec db pg_isready meldet „accepting connections“. Die Datenbank hat keinen Internetzugang, web dagegen schon, weil es zusätzlich im Netz frontend hängt. Mehr in der Anleitung zu Docker Compose und Stacks.
Troubleshooting
- „ping: bad address 'db'“: Container im Default-Bridge-Netz. Eigenes Netz anlegen (Schritt 2 und 3).
- Dienst trotz UFW-Block erreichbar: Port an
127.0.0.1binden (Schritt 4). - „Permission denied“ beim Schreiben ins Volume: UID/GID passen nicht, Lösungen in Schritt 8.
- --mount-Fehler „bind source path does not exist“: Host-Verzeichnis vorher anlegen oder
-vnutzen, wenn es automatisch entstehen darf. - Zielordner im Bind-Mount leer: Der Bind-Mount verdeckt die Image-Dateien; zur Vorbefüllung ein Named Volume nutzen.
- Daten nach Aufräumen weg:
docker volume pruneunddocker system prune --volumeslöschen ungenutzte Volumes samt Daten. Vorherdocker volume lsprüfen. - host-Modus: Port-Konflikt: Der Container belegt Host-Ports direkt. Bridge-Netz mit Port-Mapping nutzen.
- PostgreSQL-Container beendet sich sofort:
POSTGRES_PASSWORDfehlt, oder bei Version 18 liegt das Volume unter/var/lib/postgresql/data(„unused mount/volume“); richtig ist/var/lib/postgresql.
Häufige Fragen
Warum funktioniert ping über den Container-Namen nicht?
Default-Bridge-Netz ohne Namensauflösung; eigenes Netz wie in Schritt 2 und 3 anlegen.
Brauche ich -p, damit zwei Container miteinander reden?
Nein. -p dient nur dem Zugriff vom Host oder von außen (Schritt 4).
Named Volume oder Bind-Mount – was nehme ich?
Named Volume für Anwendungs- und Datenbankdaten, Bind-Mount für Konfiguration oder Quellcode (Tabelle in Schritt 6).
Wie sichere ich ein Volume regelmäßig?
Mit dem tar-Befehl aus Schritt 7 per Cron; Datenbanken vorher stoppen oder dumpen.
Überlebt ein Named Volume das Löschen des Containers?
Ja. Named Volumes entfernen nur docker volume rm oder docker volume prune; anonyme Volumes löschen docker run --rm und docker rm -v mit.
-v oder --mount?
Beide funktionieren, empfohlen ist --mount (Unterschiede in Schritt 6).
Fazit
Zwei Regeln tragen fast jedes Setup: ein user-defined Bridge-Netz je Anwendung und Named Volumes für Daten. Ports nur bei Bedarf veröffentlichen, interne Dienste an 127.0.0.1 binden, UID/GID beachten; Vorlage ist Schritt 9.
Weiterführende Anleitungen und Quellen
- Docker installieren auf Debian und Ubuntu – falls die Engine noch fehlt.
- Docker Compose: Grundlagen und Stacks – Netze und Volumes in Stacks deklarieren.
- nginx Reverse Proxy mit TLS einrichten – lokal gebundene Dienste per HTTPS freigeben.
- Alle Docker-Anleitungen
Quellen: Docker Docs – Bridge network driver, Docker Docs – Volumes, Docker Docs – Bind mounts und Docker Docs – Networking in Compose.


