Docker-Netzwerke auf einem VPS: Bridge, Host und Macvlan erklärt

11 Min. Lesezeit·Matthieu·vpsipv6docker-composenetworkingdocker|

Wie Docker Bridge-, Host- und Macvlan-Netzwerke auf einem einzelnen VPS funktionieren. Custom-Bridge-DNS, Port-Publishing, IPv6-Konfiguration und Netzwerkisolierung mit Docker Compose.

Sie haben einen VPS. Mehrere Container müssen miteinander und mit der Außenwelt kommunizieren. Docker bietet drei relevante Netzwerktreiber (Network Drivers): Bridge, Host und Macvlan. Jeder macht unterschiedliche Kompromisse zwischen Isolation, Leistung und Komfort.

Dieser Artikel erklärt, wann Sie welchen Treiber wählen sollten, wie Sie Container über benutzerdefinierte Bridge-Netzwerke und Docker Compose verbinden und wie Sie die typischen Fehler vermeiden, die Services unerreichbar oder versehentlich öffentlich zugänglich machen.

Voraussetzungen: Grundkenntnisse der Docker-CLI und Vertrautheit mit Docker Compose.

Was ist der Unterschied zwischen Docker Bridge und Host Networking?

Bridge-Netzwerke geben jedem Container einen eigenen Netzwerk-Namespace mit einem virtuellen Ethernet-Paar (veth), das ihn mit einer Software-Bridge auf dem Host verbindet. Container erhalten private IPs (typischerweise im Bereich 172.17.0.0/16) und erreichen die Außenwelt über NAT, verwaltet durch iptables. Host-Networking entfernt den Netzwerk-Namespace vollständig: Der Container teilt den Netzwerk-Stack des Hosts und bindet sich direkt an Host-Ports ohne Adressübersetzung.

Hier ein Vergleich auf einen Blick:

Kriterium Custom Bridge Host Macvlan
Netzwerkisolierung Vollständig (eigener Namespace) Keine (teilt den Host) Vollständig (eigene MAC-Adresse)
DNS-Auflösung Automatisch nach Containername Nutzt Host-DNS Kein eingebautes DNS
Port-Mapping Erforderlich (-p) Nicht nötig (direkte Bindung) Nicht nötig (echte IP)
Performance-Overhead Gering (NAT + veth) Keiner Keiner
Sicherheitsrisiko Niedrig (standardmäßig isoliert) Hoch (keine Netzwerkgrenze) Mittel (Promiscuous Mode erforderlich)
VPS-Relevanz Erste Wahl Monitoring, hoher Durchsatz Selten nutzbar

Warum sollten Sie ein benutzerdefiniertes Bridge-Netzwerk verwenden?

Das Standard-bridge-Netzwerk (genannt docker0) bietet keine DNS-Auflösung zwischen Containern. Container können sich nur über IP-Adressen erreichen, die sich bei jedem Neustart ändern. Benutzerdefinierte Bridge-Netzwerke bieten automatisches DNS, bessere Isolation und die Möglichkeit, Container im laufenden Betrieb zu verbinden oder zu trennen. Verwenden Sie für alles ein benutzerdefiniertes Bridge-Netzwerk. Betrachten Sie die Standard-Bridge als Altlast.

Das DNS-Problem

Wenn Docker startet, erstellt es ein Standard-Bridge-Netzwerk. Jeder Container ohne explizites --network-Flag landet darauf. Aber Container auf der Standard-Bridge können sich nicht über den Namen auflösen:

# Start two containers on the default bridge
docker run -d --name web nginx:alpine
docker run -d --name client alpine sleep 3600

# Try name resolution - this fails
docker exec client ping -c1 web
# ping: bad address 'web'

Versuchen Sie das Gleiche mit einer benutzerdefinierten Bridge:

# Create a custom bridge network
docker network create app-net

# Start containers on it
docker run -d --name web2 --network app-net nginx:alpine
docker run -d --name client2 --network app-net alpine sleep 3600

# Name resolution works
docker exec client2 ping -c1 web2
# PING web2 (172.18.0.2): 56 data bytes
# 64 bytes from 172.18.0.2: seq=0 ttl=64 time=0.089 ms

Wie Dockers eingebetteter DNS-Server funktioniert

Jeder Container in einem benutzerdefinierten Netzwerk erhält 127.0.0.11 als DNS-Server. Das ist Dockers eingebetteter DNS-Server. Er löst Containernamen und Service-Aliase in ihre aktuellen IP-Adressen auf. Wenn der Name keinem Container entspricht, leitet er die Anfrage an die auf dem Host konfigurierten DNS-Server weiter.

docker exec client2 cat /etc/resolv.conf
# nameserver 127.0.0.11

Der DNS-Server läuft im Netzwerk-Namespace des Containers, die eigentliche Auflösung findet aber im Docker-Daemon auf dem Host statt. Um Konflikte mit Diensten zu vermeiden, die Port 53 im Container nutzen, verwendet Dockers DNS-Listener intern einen zufälligen hohen Port und leitet Anfragen über iptables-Regeln um.

Es gibt keine IPv6-Äquivalenzadresse. Die Adresse 127.0.0.11 funktioniert auch in reinen IPv6-Containern.

Der eingebettete DNS-Server gibt einen TTL von 600 Sekunden (10 Minuten) für Containernamen-Einträge zurück. Das ist relevant für Blue-Green-Deployments: Wenn Sie einen Container ersetzen, können andere Container die alte IP noch bis zu 10 Minuten lang auflösen. Anwendungen mit aggressivem DNS-Caching (Java cached standardmäßig unbegrenzt) halten veraltete Adressen noch länger vor.

Container im selben benutzerdefinierten Bridge-Netzwerk stellen einander alle Ports ohne -p-Flag zur Verfügung. Port-Publishing steuert nur den Zugriff von außerhalb des Netzwerks (Host oder Internet). Zwei Container auf app-net können auf jedem Port frei kommunizieren.

Standard-Bridge vs. benutzerdefinierte Bridge

Funktion Standard-Bridge (docker0) Benutzerdefinierte Bridge
DNS nach Containername Nein Ja
Dynamisches Verbinden/Trennen Nein (Container-Neustart erforderlich) Ja
Pro Netzwerk konfigurierbar Nein (daemon.json-Bearbeitung + Neustart nötig) Ja
Isolation von anderen Stacks Nein (alle nicht zugewiesenen Container teilen es) Ja
Für Produktion empfohlen Nein Ja

Wann sollten Sie Host-Networking auf einem VPS verwenden?

Verwenden Sie Host-Networking, wenn ein Container maximale Netzwerkleistung benötigt oder sich dynamisch an viele Ports binden muss. Der Container teilt direkt den Netzwerk-Stack des Hosts und umgeht NAT und die virtuelle Ethernet-Bridge. Das eliminiert den messbaren Overhead für durchsatzintensive Workloads.

Typische Anwendungsfälle auf einem VPS:

  • Monitoring-Agenten wie Prometheus Node-Exporter oder Netdata, die alle Host-Interfaces und den Traffic sehen müssen
  • DNS-Server, die auf der tatsächlichen IP des Hosts lauschen müssen
  • Leistungskritische Dienste, bei denen der NAT-Overhead zählt (hohe Paketraten, UDP-intensive Workloads)
# Run a container with host networking
docker run -d --name node-exporter --network host \
  prom/node-exporter:latest

Das --publish-Flag wird bei Host-Networking ignoriert. Der Container bindet sich direkt an Host-Ports. Wenn Port 9100 bereits auf dem Host belegt ist, startet der Container nicht.

Sicherheits-Kompromiss

Host-Networking bietet dieselbe Netzwerkisolierung wie das direkte Ausführen des Prozesses auf dem Host: keine. Der Container kann alle Netzwerk-Interfaces sehen, sich an jeden Port binden und Traffic mitlesen. Verwenden Sie es nur mit einem konkreten Grund.

# Verify which ports a host-networked container opened
ss -tlnp | grep node_exporter
# LISTEN 0 4096 *:9100 *:* users:(("node_exporter",pid=12345,fd=3))

Achten Sie genau darauf: Diese Ausgabe zeigt, dass der Prozess auf *:9100 lauscht, also auf allen Interfaces. Bei Bridge-Networking steuern Sie das mit dem -p-Flag. Bei Host-Networking entscheidet die Anwendung selbst, woran sie sich bindet.

Was ist Macvlan-Networking und brauchen Sie es auf einem VPS?

Macvlan weist jedem Container eine eigene MAC-Adresse zu und lässt ihn als separates physisches Gerät im Netzwerk erscheinen. Der Container erhält eine echte IP aus Ihrem LAN-Subnetz und ist direkt erreichbar, ohne Port-Mapping.

Auf einem VPS brauchen Sie Macvlan fast sicher nicht. Die Gründe:

  • Die meisten Cloud-Anbieter blockieren es. Macvlan erfordert, dass die physische NIC im Promiscuous Mode arbeitet. VPS-Hypervisoren blockieren das in der Regel auf Ebene des virtuellen Switches.
  • Kein LAN zum Beitreten. Ihr VPS hat ein öffentliches Interface. Macvlan ist für Umgebungen konzipiert, in denen Container eigene Adressen in einem bestehenden LAN erhalten sollen, etwa ein Heimlabor mit IoT-Geräten oder Legacy-Anwendungen, die eine dedizierte IP erwarten.
  • Host-Container-Kommunikation funktioniert nicht. Der Linux-Kernel verhindert konstruktionsbedingt, dass ein Macvlan-Container mit dem Host kommuniziert, auf dem er läuft. Sie brauchen Workarounds wie ein zweites Bridge-Netzwerk.

Wenn Ihr VPS-Anbieter einen dedizierten Server mit vollem NIC-Zugriff und mehreren IPs bereitstellt, wird Macvlan praktikabel. Für Standard-VPS-Deployments bleiben Sie bei benutzerdefinierten Bridge-Netzwerken.

Zur Referenz: So erstellen Sie ein Macvlan-Netzwerk (Sie werden dies auf einem VPS wahrscheinlich nicht brauchen):

docker network create -d macvlan \
  --subnet=192.168.1.0/24 \
  --gateway=192.168.1.1 \
  -o parent=eth0 \
  macnet

Die parent-Option gibt das Host-Interface an, an das angehängt wird. Der Container erhält eine eigene IP aus dem Subnetz und eine eigene MAC-Adresse. Andere Geräte im LAN können ihn direkt erreichen.

Was ist der Unterschied zwischen Expose und Publish eines Docker-Ports?

EXPOSE im Dockerfile ist Dokumentation. Es deklariert, auf welchem Port die Anwendung lauscht. Es öffnet diesen Port nicht zum Host oder zur Außenwelt. --publish (oder -p) zur Laufzeit erstellt ein tatsächliches Port-Mapping vom Host zum Container. Ohne -p erreicht kein externer Traffic den Container.

# In your Dockerfile - this is documentation only
EXPOSE 8080
# This actually maps host:8080 -> container:8080
docker run -d -p 8080:8080 myapp

# This binds to localhost only - external traffic cannot reach it
docker run -d -p 127.0.0.1:8080:8080 myapp

Das 127.0.0.1-Binding-Pattern

Wenn Sie Services hinter einem Reverse Proxy wie Nginx, Traefik oder Caddy betreiben , veröffentlichen Sie Container-Ports nur auf 127.0.0.1. Das verhindert direkten Zugriff aus dem Internet und zwingt allen Traffic durch Ihren Reverse Proxy, wo TLS-Terminierung, Rate-Limiting und Zugriffskontrolle stattfinden.

# docker-compose.yml - binding to localhost only
services:
  app:
    image: myapp:latest
    ports:
      - "127.0.0.1:3000:3000"  # Only reachable from localhost

Überprüfen Sie die Bindung:

ss -tlnp | grep 3000
# LISTEN 0 4096 127.0.0.1:3000 0.0.0.0:* users:(("docker-proxy",...))

Achten Sie genau darauf: Die Ausgabe zeigt 127.0.0.1:3000, nicht 0.0.0.0:3000. Das bestätigt, dass externer Traffic über die öffentliche IP Ihres VPS Port 3000 nicht direkt erreichen kann.

Ohne das Präfix 127.0.0.1 veröffentlicht Docker standardmäßig auf allen Interfaces. Auf einem VPS mit öffentlicher IP bedeutet das: Ihr Service ist im Internet erreichbar, unter Umgehung Ihres Reverse Proxys und möglicherweise Ihrer Firewall.

Port-Publishing-Kurzreferenz

Syntax Bindet an Erreichbar von
-p 8080:80 0.0.0.0:8080 + [::]:8080 Überall (IPv4 + IPv6)
-p 127.0.0.1:8080:80 127.0.0.1:8080 Nur Localhost
-p 0.0.0.0:8080:80 0.0.0.0:8080 Alle IPv4-Interfaces
-p [::1]:8080:80 [::1]:8080 Nur IPv6-Localhost

Wie isolieren Sie Container-Netzwerke auf einem einzelnen VPS?

Erstellen Sie separate Bridge-Netzwerke für jeden Anwendungs-Stack. Container in verschiedenen Netzwerken können nicht kommunizieren, es sei denn, Sie verbinden sie explizit mit einem gemeinsamen Netzwerk. Für Datenbanken und interne Dienste verwenden Sie das Flag internal: true, um jeglichen ausgehenden Internetzugriff zu blockieren.

Multi-Netzwerk-Architektur

Ein typisches Produktions-Setup auf einem VPS sieht so aus:

                 Internet
                    |
              [Reverse Proxy]
               /          \
         [frontend]    [api-net]
            |             |
          webapp        api-server
                          |
                     [db-net: internal]
                          |
                       postgres

Der Reverse Proxy ist mit frontend und api-net verbunden. Der API-Server ist mit api-net und db-net verbunden. PostgreSQL befindet sich nur auf db-net, ohne Route ins Internet.

Hier die Docker-Compose-Datei für dieses Setup:

services:
  proxy:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    networks:
      - frontend
      - api-net

  webapp:
    image: mywebapp:latest
    networks:
      - frontend

  api:
    image: myapi:latest
    environment:
      - DATABASE_URL=postgresql://appuser:${DB_PASSWORD}@db:5432/appdb
    networks:
      - api-net
      - db-net

  db:
    image: postgres:16-alpine
    environment:
      - POSTGRES_PASSWORD_FILE=/run/secrets/db_password
    volumes:
      - pgdata:/var/lib/postgresql/data
    networks:
      - db-net

networks:
  frontend:
    driver: bridge
  api-net:
    driver: bridge
  db-net:
    driver: bridge
    internal: true  # No internet access for the database network

volumes:
  pgdata:

Das internal: true bei db-net bedeutet, dass PostgreSQL das Internet nicht erreichen kann. Es kann keine Updates herunterladen, keinen externen Server kontaktieren und nicht als Pivot für ausgehende Verbindungen missbraucht werden. Der API-Server kann die Datenbank erreichen, weil er sowohl an api-net als auch an db-net angeschlossen ist.

Netzwerkisolierung überprüfen

Nachdem Sie den Stack gestartet haben, bestätigen Sie die Isolation:

# List networks created by Compose
docker network ls --filter "name=myproject"

# Inspect a network to see which containers are connected
docker network inspect myproject_db-net --format '{{range .Containers}}{{.Name}} {{end}}'
# myproject-api-1 myproject-db-1

# Confirm the database cannot reach the internet
docker exec myproject-db-1 ping -c1 -W2 8.8.8.8
# ping: sendto: Network unreachable

Achten Sie genau darauf: „Network unreachable" bestätigt, dass das internal: true-Flag funktioniert. Der Datenbank-Container hat kein Standard-Gateway.

Wie aktivieren Sie IPv6 für Docker-Container?

Aktivieren Sie IPv6 global in /etc/docker/daemon.json und dann pro Netzwerk mit einem Subnetz. Docker unterstützt Dual-Stack (IPv4 + IPv6) nativ unter Linux. Der Parameter ip6tables, standardmäßig aktiviert, verwaltet IPv6-Firewall-Regeln für Container.

Schritt 1: Den Daemon konfigurieren

Bearbeiten Sie /etc/docker/daemon.json:

{
  "ipv6": true,
  "fixed-cidr-v6": "fd00:dead:beef::/64",
  "ip6tables": true,
  "default-address-pools": [
    { "base": "172.17.0.0/16", "size": 24 },
    { "base": "fd00:dead:beef::/48", "size": 64 }
  ]
}

fixed-cidr-v6 weist der Standard-Bridge ein IPv6-/64-Subnetz zu. Der Block default-address-pools teilt Docker mit, wie Subnetze für neue Netzwerke zugewiesen werden: /24-Blöcke aus dem IPv4-Bereich und /64-Blöcke aus dem IPv6-Bereich.

Verwenden Sie ein ULA-Präfix (fd00::/8) für privaten Container-zu-Container-Traffic. Wenn Ihrem VPS ein öffentlicher IPv6-Bereich von Ihrem Anbieter zugewiesen wurde, können Sie ein Subnetz daraus für Container mit öffentlichen IPv6-Adressen verwenden.

Docker neu starten:

sudo systemctl restart docker

Überprüfen Sie, ob IPv6 aktiviert ist:

docker network inspect bridge --format '{{.EnableIPv6}}'
# true

Schritt 2: Ein Dual-Stack-Netzwerk erstellen

docker network create --ipv6 --subnet 172.20.0.0/24 --subnet fd00:dead:beef:1::/64 app-v6

Oder in Docker Compose:

networks:
  app-v6:
    enable_ipv6: true
    ipam:
      config:
        - subnet: 172.20.0.0/24
        - subnet: fd00:dead:beef:1::/64

Schritt 3: Dual-Stack-Konnektivität überprüfen

docker run --rm --network app-v6 alpine ip -6 addr show eth0
# inet6 fd00:dead:beef:1::2/64 scope global

IPv6 wird nur auf Docker-Daemons unter Linux unterstützt. Auf älteren Systemen müssen Sie möglicherweise das Kernel-Modul ip6_tables laden, bevor Sie IPv6-Netzwerke erstellen:

sudo modprobe ip6_tables

Wie definieren Sie Netzwerke in Docker Compose?

Docker Compose erstellt automatisch ein Standardnetzwerk für jedes Projekt. Jeder Service in der Compose-Datei tritt diesem Netzwerk bei und kann andere Services über ihren Servicenamen auflösen. Für die meisten Einzelstack-Anwendungen reicht dieses Standardverhalten aus.

Wenn Sie mehrere Netzwerke zur Isolation benötigen, definieren Sie sie explizit im networks-Abschnitt:

services:
  web:
    image: nginx:alpine
    ports:
      - "127.0.0.1:8080:80"
    networks:
      - public

  app:
    image: node:20-alpine
    networks:
      - public
      - private

  redis:
    image: redis:7-alpine
    networks:
      - private

networks:
  public:
    driver: bridge
  private:
    driver: bridge
    internal: true

In dieser Datei kann web über das public-Netzwerk app erreichen. app kann sowohl web als auch redis erreichen. redis kann nur app über private erreichen und hat keinen Internetzugang.

Host-Networking in Compose

services:
  node-exporter:
    image: prom/node-exporter:latest
    network_mode: host
    pid: host
    restart: unless-stopped

network_mode: host ersetzt jede networks-Definition. Sie können den Host-Modus nicht mit benutzerdefinierten Netzwerken für denselben Service kombinieren.

Verbindung zu externen Netzwerken

Wenn ein Netzwerk außerhalb von Compose erstellt wurde (von einem anderen Stack oder manuell), referenzieren Sie es als extern:

networks:
  shared-proxy:
    external: true

Das ermöglicht mehreren Compose-Projekten, ein Netzwerk gemeinsam zu nutzen. Ein gängiges Muster: Ein Compose-Stack betreibt den Reverse Proxy und erstellt das Netzwerk. Andere Stacks deklarieren es als extern und verbinden ihre Services damit.

Docker-Netzwerk-Befehle: Kurzreferenz

Befehl Funktion
docker network ls Alle Netzwerke auflisten
docker network create mynet Ein Bridge-Netzwerk erstellen
docker network create --ipv6 --subnet fd00::/64 mynet Ein Dual-Stack-Netzwerk erstellen
docker network inspect mynet Netzwerkdetails anzeigen (Subnetz, Container, Optionen)
docker network connect mynet container1 Einen laufenden Container an ein Netzwerk anhängen
docker network disconnect mynet container1 Einen Container von einem Netzwerk trennen
docker network prune Alle ungenutzten Netzwerke entfernen
docker network rm mynet Ein bestimmtes Netzwerk entfernen

Skalierungsgrenzen

Bridge-Netzwerke werden instabil, wenn mehr als 1.000 Container mit einem einzelnen Netzwerk verbunden sind. Das ist eine Einschränkung des Linux-Kernels beim Bridge-Device. Wenn Sie viele Container betreiben, verteilen Sie diese auf mehrere Netzwerke nach Funktion, anstatt alles auf eine einzige Bridge zu legen.

Etwas funktioniert nicht?

Container können sich nicht gegenseitig über den Namen auflösen. Sie befinden sich wahrscheinlich auf der Standard-Bridge. Erstellen Sie ein benutzerdefiniertes Netzwerk und verbinden Sie beide Container damit. Prüfen Sie mit docker inspect <container> --format '{{json .NetworkSettings.Networks}}'.

Port ist veröffentlicht, aber von außen nicht erreichbar. Prüfen Sie, ob er an 127.0.0.1 statt 0.0.0.0 gebunden ist. Führen Sie ss -tlnp | grep <port> auf dem Host aus. Prüfen Sie auch Ihre Firewall-Regeln.

Container kann das Internet nicht erreichen. Das Netzwerk hat möglicherweise internal: true gesetzt. Prüfen Sie mit docker network inspect <network> --format '{{.Internal}}'. Wenn true zurückkommt, blockiert das Netzwerk ausgehenden Traffic absichtlich.

IPv6 funktioniert nicht in Containern. Überprüfen Sie, ob ip6tables in daemon.json aktiviert ist. Auf älteren Kerneln laden Sie das Modul: sudo modprobe ip6_tables. Prüfen Sie, ob IPv6 im Netzwerk aktiviert ist: docker network inspect <network> --format '{{.EnableIPv6}}'.

„Address already in use" bei Host-Networking. Ein anderer Prozess (oder Container) belegt bereits diesen Port. Finden Sie ihn mit ss -tlnp | grep <port>. Host-Networking hat kein Port-Mapping, daher sind Konflikte direkt.

Nächste Schritte

Ihr Container-Netzwerk steht. Was als Nächstes kommt:

  • Einen Reverse Proxy für TLS und Routing einrichten
  • Dockers Firewall-Umgehung beheben, damit veröffentlichte Ports Ihre UFW/nftables-Regeln respektieren
  • Ressourcenlimits und Healthchecks zu Ihren Compose-Services hinzufügen
  • Zurück zur Docker-auf-VPS-Übersicht für die vollständige Produktions-Checkliste

Copyright 2026 Virtua.Cloud. Alle Rechte vorbehalten. Dieser Inhalt ist ein Originalwerk des Virtua.Cloud-Teams. Vervielfältigung, Wiederveröffentlichung oder Weiterverbreitung ohne schriftliche Genehmigung ist untersagt.

Bereit, es selbst auszuprobieren?

Meistern Sie Docker-Netzwerke auf einem dedizierten VPS.

VPS-Angebote ansehen