Docker-Netzwerke und Volumes richtig nutzen
Container finden sich nicht und Daten sind nach dem Neustart weg? Diese Anleitung zeigt Schritt für Schritt, wie du Docker-Netzwerke und Volumes richtig nutzt: user-defined Bridge mit DNS, Ports veröffentlichen vs. interne Kommunikation, Named Volumes vs. Bind-Mounts, Backup per tar, UID/GID-Fallen.

Zwei Container, die sich nicht finden, und eine Datenbank, deren Inhalt nach dem nächsten docker rm verschwunden ist – das sind die häufigsten Stolpersteine im Docker-Alltag. Beide haben dieselbe Ursache: Docker-Netzwerke und Volumes werden nicht richtig genutzt. In dieser Anleitung lernst du, wie Container zuverlässig über ihre Namen kommunizieren, wann du einen Port wirklich nach außen öffnen musst und wie du Daten so ablegst, dass sie das Löschen eines Containers überleben. Alle Befehle sind copy-paste-fertig für einen Linux-Server (Debian 12 oder Ubuntu 24.04 LTS). Diese Anleitung ergänzt unsere Grundlagen zur Docker-Installation und zu Docker Compose und Stacks.
Kurzfassung: Lege für Multi-Container-Apps immer ein eigenes (user-defined) Bridge-Netz mit docker network create an – nur dort funktioniert die DNS-Auflösung über Container-Namen (eingebetteter DNS unter 127.0.0.11). Das alte Default-Bridge-Netz kann nur über IP kommunizieren. Veröffentliche Ports mit -p ausschließlich dann, wenn der Host bzw. das Internet zugreifen soll; rein interne Kommunikation zwischen Containern im selben Netz braucht kein -p. Speichere Daten in Named Volumes (überleben das Löschen des Containers), nutze Bind-Mounts für konkrete Host-Pfade. Sichere Volumes per tar und achte auf UID/GID, um Permission-Fehler zu vermeiden.
Voraussetzungen
- Ein Linux-Server mit Debian 12 oder Ubuntu 24.04 LTS und lauffähiger Docker Engine (siehe Docker installieren).
- Ein Benutzer in der Gruppe
dockeroder Zugriff persudo. - Grundkenntnisse von
docker runund – für die Compose-Beispiele – Docker Compose v2 (docker compose). - Internetzugang, um die verwendeten Images (
nginx,postgres,alpine,ubuntu) zu ziehen.
Prüfe vorab kurz, 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. Schau sie dir an:
docker network ls
NETWORK ID NAME DRIVER SCOPE
6f1a... bridge bridge local
9c2b... host host local
3d4e... none null localDas vorinstallierte Netz bridge ist das Default-Bridge-Netz (intern docker0). Sein entscheidender Nachteil: Container darin erreichen sich nur über die IP-Adresse. Eine Namensauflösung gibt es hier nicht (abgesehen vom veralteten, abgekündigten --link). Genau deshalb scheitert in diesem Netz ein simples ping db.
Ein user-defined Bridge-Netz – also eines, das du selbst anlegst – bringt dagegen zwei Dinge mit, die du fast immer brauchst:
- Automatische DNS-Auflösung über Container-Namen und Aliase. Docker betreibt dafür einen eingebetteten DNS-Server unter der Adresse
127.0.0.11; externe Lookups leitet er an die DNS-Server des Hosts weiter. - Isolation: Nur Container im selben user-defined Netz können miteinander reden. Andere Netze sind getrennt.
Für jede Multi-Container-Anwendung ist ein eigenes Netz daher Pflicht – Docker empfiehlt das selbst. Ein weiterer Praxisvorteil: Laufende Container lassen sich mit docker network connect bzw. disconnect jederzeit an- und abhängen. Beim Default-Bridge-Netz müsstest du den Container dafür neu erstellen.
Schritt 2: Ein eigenes Netzwerk anlegen und inspizieren
Lege ein user-defined Bridge-Netz an. Ohne weitere Angabe nutzt Docker automatisch den Treiber bridge – inklusive DNS:
docker network create my-netWenn du Subnetz und Gateway selbst kontrollieren willst (etwa um Kollisionen mit anderen Netzen zu vermeiden), gib sie explizit an:
docker network create -d bridge \
--subnet=172.28.0.0/16 \
--gateway=172.28.0.1 \
br0Mit inspect siehst du Subnetz, Gateway und alle aktuell verbundenen Container samt ihrer IPs:
docker network inspect my-netDie wichtigsten Verwaltungsbefehle im Überblick:
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 entfernenSchritt 3: DNS-Auflösung über Container-Namen testen
Jetzt der Praxistest. Starte zwei Container im selben Netz – einen Webserver, der nach außen erreichbar sein soll, und eine Datenbank, die nur intern ansprechbar ist:
# 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 postgres:18Der Container web erreicht die Datenbank jetzt schlicht unter dem Hostnamen db – ohne dass du eine IP kennen musst. Prüfe die Namensauflösung mit einem Wegwerf-Container im selben Netz:
docker run --rm --network my-net alpine ping -c2 dbBekommst du Antworten, funktioniert der eingebettete DNS. Zum Gegentest: Im Default-Bridge-Netz (also ohne --network my-net) würde derselbe Aufruf mit bad address db scheitern. Das ist der praktische Unterschied zwischen den beiden Netztypen.
Schritt 4: Ports veröffentlichen vs. interne Kommunikation
Hier liegt der häufigste Denkfehler. Merke dir die klare Trennlinie:
- -p / --publish macht einen Container-Port vom Host (und damit potenziell vom Internet) aus erreichbar.
-p 8080:80bedeutet: Host-Port 8080 leitet auf Container-Port 80. - Für die reine Kommunikation zwischen Containern im selben Netz brauchst du kein
-p. Der Container-Port ist intern ohnehin erreichbar.EXPOSEund-pbetreffen nur den Zugriff von außen.
Konkret heißt das: Der Datenbank-Port (z. B. 5432 bei PostgreSQL) gehört niemals nach außen geöffnet, wenn nur der Web-Container darauf zugreift. Jedes überflüssige -p vergrößert nur die Angriffsfläche.
Vorsicht: -p umgeht die UFW-Firewall
Ein -p 8080:80 bindet standardmäßig an alle Interfaces (0.0.0.0). Docker schreibt dafür eigene iptables-Regeln und kann so die UFW-Firewall umgehen – der Dienst ist dann erreichbar, obwohl UFW ihn vermeintlich blockt. Soll ein Dienst nur lokal (etwa für einen Reverse Proxy auf demselben Host) erreichbar sein, binde ihn explizit an die Loopback-Adresse:
# Nur auf dem Host selbst erreichbar, nicht von aussen:
docker run -d --name web --network my-net -p 127.0.0.1:8080:80 nginx:latestWie du einen solchen lokal gebundenen Dienst sauber per TLS nach außen gibst, zeigt unsere Anleitung zum nginx Reverse Proxy mit TLS.
Schritt 5: host- und none-Modus gezielt einsetzen
Neben dem Bridge-Treiber gibt es zwei weitere Modi, die du per --network auswählst:
ModusVerhaltenEinsatz
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
docker run -d --network host nginx:latest
# none-Modus: vollstaendige Netzwerk-Isolation
docker run -d --network none alpine sleep 1000Der host-Modus teilt alle Host-Ports und ignoriert -p komplett – das birgt Port-Konflikte und ein Sicherheitsrisiko. Setze ihn auf Linux nur ein, wo er wirklich nötig ist.
Netz ohne Internetzugang (--internal)
Soll ein Netz extern komplett isoliert sein – ohne Default-Gateway und ohne Host-/Internet-Anbindung – legst du es mit --internal an. Ideal für eine Datenbankschicht, die niemals selbst ins Internet muss:
docker network create --internal isoliert-netSchritt 6: Named Volumes vs. Bind-Mounts für persistente Daten
Container sind flüchtig: Löschst du einen Container, sind seine Schreibvorgänge im Container-Dateisystem weg. Für persistente Daten gibt es zwei Wege.
AspektNamed VolumeBind-Mount
Verwaltung
vollständig von Docker (unter /var/lib/docker/volumes/NAME/_data)
konkreter Host-Pfad, von dir verwaltet
Portabilität
hoch, OS-unabhängig
abhängig von Host-Verzeichnisstruktur/OS
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)
Lege ein Named Volume an und hänge es ein. Stand 2025/2026 empfiehlt Docker die --mount-Syntax, weil sie expliziter ist; -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:latestEin Bind-Mount bindet einen konkreten (absoluten) Host-Pfad ein. Das :ro bzw. readonly macht ihn schreibgeschützt:
# Bind-Mount, read-only, mit -v
docker run -d --name app -v /srv/appdata:/data:ro nginx:latest
# Bind-Mount mit --mount (Quelle MUSS existieren)
docker run -d --name app --mount type=bind,src=/srv/appdata,dst=/data nginx:latestWichtige Unterschiede zwischen den Syntaxen:
- Auto-Create: Existiert die Bind-Quelle nicht, legt
-vdas Host-Verzeichnis automatisch an (als root).--mount type=bindwirft hingegen einen Fehler – das verhindert versehentliche, root-eigene Verzeichnisse auf dem Host. - Read-only: per
-v ...:rooder--mount ...,readonly. - SELinux-Labels (
z= geteilt,Z= privat) lassen sich nur mit-vsetzen, nicht mit--mount. Relevant ist das auf RHEL/Fedora; auf Debian/Ubuntu kommt in der Regel AppArmor zum Einsatz, daher bist du dort meist nicht betroffen.
Verwalten lassen sich Volumes analog zu 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!)Hinweis zu anonymen Volumes: Ohne Namen vergibt Docker einen Zufallsnamen. Mit docker run --rm werden solche anonymen Volumes beim Beenden automatisch gelöscht – Named Volumes bleiben dagegen erhalten und sorgen so für echte Persistenz.
Schritt 7: Ein Volume sichern und wiederherstellen (tar)
Named Volumes lassen sich nicht einfach kopieren – du sicherst sie über einen kurzlebigen Hilfscontainer, der das Volume einhängt und per tar in dein aktuelles Verzeichnis schreibt. Die direkteste Variante für ein Named Volume:
# 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 offizielle Backup-/Restore-Methode arbeitet mit --volumes-from, um die Volumes eines bestehenden Containers (hier dbstore) zu übernehmen:
# 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"Tipp: Stoppe datenbankbasierte Container vor dem Backup oder nutze applikationskonsistente Dumps, damit du keinen inkonsistenten Zustand sicherst.
Schritt 8: Die UID/GID-Falle vermeiden
Die häufigste Praxis-Stolperfalle bei Volumes und besonders bei Bind-Mounts sind Ownership- und Rechte-Konflikte. Hintergrund: Container laufen standardmäßig als root (UID 0). Schreibt root im Container in einen Bind-Mount, gehören die Dateien auf dem Host anschließend ebenfalls root (UID 0). Läuft deine Anwendung dagegen als nicht-root-Benutzer, bekommst du durch den UID/GID-Mismatch Permission denied.
Drei saubere Lösungen – greife nie zu dauerhaftem 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.txtDie dritte Variante ist ein Entrypoint-chown oder ein Init-Container, der die Rechte beim Start einmalig korrekt setzt, bevor die eigentliche Anwendung (als nicht-root) übernimmt. So bleiben die Daten persistent und gleichzeitig schreibbar.
Zur Erinnerung an Schritt 6: Ein leeres Named Volume wird beim ersten Mount mit dem Inhalt des Container-Zielpfads vorbefüllt. Ein Bind-Mount tut das nicht, sondern verdeckt vorhandene Container-Dateien – der Zielordner kann dann unerwartet leer wirken.
Schritt 9: Netzwerke und Volumes in Docker Compose
In der Praxis steckst du das alles in eine compose.yaml. Docker Compose legt automatisch ein Bridge-Netz namens PROJEKT_default an und hängt alle Services daran. Services erreichen sich darin über ihren Service-Namen per DNS – genau wie in Schritt 3. Das folgende Beispiel kombiniert Netz-Isolation und persistente Daten:
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/data # Named Volume, persistent
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 exec web ping -c2 db # DNS ueber den Service-Namen dbDer Web-Service erreicht die Datenbank unter dem Hostnamen db, ein Datenbank-Port wird nirgends veröffentlicht, und das backend-Netz ist per internal: true vom Internet abgeschnitten. Mit external: true könntest du stattdessen ein bereits vorhandenes Netz oder Volume einbinden. Mehr dazu in unserer Anleitung zu Docker Compose und Stacks.
Troubleshooting
- „ping db: bad address“ / Name nicht auflösbar: Die Container hängen vermutlich im Default-Bridge-Netz. Lege ein user-defined Netz an (
docker network create) und starte beide Container darin – oder nutze in Compose den Service-Namen. - Dienst trotz UFW-Block von außen erreichbar: Docker schreibt eigene iptables-Regeln und umgeht UFW. Binde den Port lokal:
-p 127.0.0.1:8080:80, statt an0.0.0.0. - „Permission denied“ beim Schreiben ins Volume: Klassischer UID/GID-Mismatch. Setze die Ownership per
chown -R UID:GIDim Volume oder starte den Container mit-u $(id -u):$(id -g). Keinchmod 777. - --mount-Fehler „bind source path does not exist“:
--mount type=binderstellt fehlende Quellen nicht. Lege das Host-Verzeichnis vorher an oder nutze-v, wenn Auto-Create erwünscht ist. - Zielordner im Bind-Mount unerwartet leer: Ein Bind-Mount verdeckt vorhandene Container-Dateien. Für Vorbefüllung mit dem Image-Inhalt nutze ein Named Volume.
- Daten nach Backup-Aufräumen weg:
docker volume pruneunddocker system prune --volumeslöschen ungenutzte Volumes mitsamt Daten. Vor dem Ausführen prüfen, was wirklich ungenutzt ist. - host-Modus: Port-Konflikt: Im
host-Modus wird-pignoriert und alle Host-Ports werden geteilt. Wechsle zu einem Bridge-Netz mit gezieltem Port-Mapping.
Häufige Fragen
Warum funktioniert ping über den Container-Namen nicht?
Weil die Container im Default-Bridge-Netz hängen. Dort gibt es keine Namensauflösung – nur über die IP-Adresse. Lege ein user-defined Bridge-Netz mit docker network create an und starte beide Container mit --network DEIN-NETZ; dann löst der eingebettete DNS-Server (127.0.0.11) die Namen auf.
Brauche ich -p, damit zwei Container miteinander reden?
Nein. -p ist ausschließlich für den Zugriff vom Host bzw. aus dem Internet nötig. Hängen beide Container im selben Netz, ist der Container-Port intern ohnehin erreichbar. Datenbank-Ports solltest du daher nie nach außen veröffentlichen.
Named Volume oder Bind-Mount – was nehme ich?
Für Anwendungs- und Datenbankdaten ein Named Volume: portabel, von Docker verwaltet und einfach zu sichern. Einen Bind-Mount nimmst du, wenn du einen konkreten Host-Pfad brauchst – etwa für Konfigurationsdateien oder Quellcode in der Entwicklung.
Wie sichere ich ein Volume regelmäßig?
Mit einem kurzlebigen Hilfscontainer, der das Volume einhängt und per tar in ein Backup-Verzeichnis schreibt (siehe Schritt 7). Diesen Befehl kannst du in ein Skript packen und per Cron einplanen. Stoppe Datenbank-Container vorher oder nutze konsistente Dumps.
Überlebt ein Named Volume das Löschen des Containers?
Ja. Named Volumes bleiben nach docker rm erhalten und müssen explizit mit docker volume rm oder docker volume prune entfernt werden. Anonyme Volumes hingegen werden bei docker run --rm automatisch mitgelöscht.
-v oder --mount?
Beide funktionieren. --mount ist expliziter und wird aktuell empfohlen. Beachte die Unterschiede: -v legt fehlende Bind-Quellen automatisch an, --mount type=bind nicht. SELinux-Labels (z/Z) gehen nur mit -v.
Fazit
Die beiden wichtigsten Regeln nimmst du aus dieser Anleitung mit: Für jede Multi-Container-App ein eigenes user-defined Bridge-Netz, damit Container sich über ihre Namen finden – und Daten gehören in Named Volumes (oder gezielt gesetzte Bind-Mounts), damit sie das Löschen eines Containers überleben. Veröffentliche Ports mit -p nur, wenn der Host wirklich zugreifen soll, binde sicherheitskritische Dienste an 127.0.0.1, und behalte die UID/GID im Blick, um Permission-Fehler zu vermeiden. Mit dem Compose-Beispiel aus Schritt 9 hast du eine saubere, isolierte und persistente Vorlage, auf der du deine Stacks aufbauen kannst.
Weiterführende Anleitungen und Quellen
- Docker installieren auf Debian und Ubuntu – die Grundlage, falls die Engine noch fehlt.
- Docker Compose: Grundlagen und Stacks – wie du Netze und Volumes in Multi-Service-Setups deklarierst.
- nginx Reverse Proxy mit TLS einrichten – lokal gebundene Container-Dienste sicher per HTTPS nach außen geben.
- Alle Docker-Anleitungen in der Kategorie Docker – weitere Schritt-für-Schritt-Guides rund um Container.
Quellen: Docker Docs – Bridge network driver, Docker Docs – Volumes, Docker Docs – Bind mounts und Docker Docs – Networking in Compose.