Netzwerkzugriff von Containern nach außen einschränken

Netzwerkzugriff von Containern nach außen einschränken

Standardmäßig können Container frei ins Internet kommunizieren — das ist für Datenbanken und interne Services oft unnötig und ein Sicherheitsrisiko.

Das Standard-Verhalten

docker run --rm alpine wget -qO- https://example.com   # Funktioniert problemlos
  • Jeder Container kann standardmäßig beliebige externe Verbindungen aufbauen. Das ist ein Problem wenn:
  • Eine Datenbank Daten nach außen exfiltrieren könnte
  • Kompromittierte Container Command-and-Control-Server kontaktieren
  • Software in Containern unerwünscht Telemetrie sendet

Lösung 1: Internes Netzwerk

networks:
  backend:
    internal: true    # Kein Internet-Zugriff aus diesem Netz
services:
  db:
    image: postgres:16
    networks:
      - backend       # Nur interner Zugriff, kein Internet

  cache:
    image: redis
    networks:
      - backend

  app:
    image: myapp
    networks:
      - backend
      - frontend      # Frontend-Netz HAT Internet-Zugriff (für externe APIs)

networks:
  backend:
    internal: true
  frontend:           # Standard: Internet-Zugriff erlaubt

Container im backend-Netz können sich gegenseitig erreichen, aber keine externen Verbindungen aufbauen. Die App ist in beiden Netzen und kann externe Services aufrufen.

Testen

# Sollte fehlschlagen
docker compose exec db wget -qO- https://example.com
# wget: bad address 'example.com'

# Sollte funktionieren
docker compose exec app wget -qO- https://api.example.com

Lösung 2: Outbound-Proxy

Wenn Container kontrollierten Internet-Zugriff brauchen (nur bestimmte Domains), via HTTP-Proxy routen:

services:
  app:
    image: myapp
    environment:
      HTTP_PROXY: http://squid:3128
      HTTPS_PROXY: http://squid:3128
      NO_PROXY: db,cache,localhost

  squid:
    image: ubuntu/squid
    volumes:
      - ./squid.conf:/etc/squid/squid.conf:ro
    networks:
      - proxy-net
      - internet

networks:
  proxy-net:
    internal: true
  internet:

Squid als Proxy erlaubt nur bestimmte Ziel-Domains:

# squid.conf
acl allowed_domains dstdomain .example.com .api.stripe.com
http_access allow allowed_domains
http_access deny all

Lösung 3: DNS-basierte Einschränkung

Eigener DNS-Server, der nur bestimmte Domains auflöst:

services:
  app:
    image: myapp
    dns:
      - 172.20.0.10   # Interner DNS-Server

  dns:
    image: coredns/coredns
    networks:
      backend:
        ipv4_address: 172.20.0.10
    volumes:
      - ./Corefile:/Corefile:ro

DNS-basiertes Blocking ist einfach zu umgehen (direkte IP-Verbindungen) und daher nur als ergänzende Maßnahme geeignet.

Praxisbeispiele

Datenbank komplett isolieren:

services:
  db:
    image: postgres:16
    networks:
      - db-net

networks:
  db-net:
    internal: true

Air-gapped Setup (keine externen Verbindungen überhaupt):

networks:
  default:
    internal: true

Alle Services im Stack bekommen kein Internet — nützlich für isolierte Test-Umgebungen.

Was internal: true nicht abdeckt

  • internal: true verhindert Internet-Zugriff durch Entfernen der Default-Gateway-Route. Es verhindert nicht:
  • Kommunikation zwischen Containern im selben Netz
  • Zugriff auf den Docker-Host selbst (über die Bridge-IP)
  • Direkten Zugriff über Host-Netzwerk (network_mode: host)

Docker-eigene iptables-Regeln

Docker verwaltet iptables-Regeln selbst. Eigene iptables-Regeln können mit Docker in Konflikt geraten:

# Docker überschreibt FORWARD-Chain — eigene Rules in DOCKER-USER setzen
iptables -I DOCKER-USER -j DROP    # Blockiert alle Container-zu-Container-Verbindungen

Für komplexes Traffic-Shaping ist internal: true mit einem Proxy-Container die robustere Lösung als direkte iptables-Manipulation.

Die einfachste und zuverlässigste Methode bleibt internal: true in Compose — kein Extra-Tool, kein iptables-Wissen nötig, und für die meisten Anwendungsfälle ausreichend.