Nach dem Starten des ersten Containers steht man vor der Frage: Wie bekomme ich einen Dienst aus dem Container nach draußen? Incus bietet dafür mehrere Wege – der flexibelste ist das Proxy-Device. Dieser Beitrag erklärt, wie es funktioniert, und vor allem, was der Unterschied zwischen mit und ohne NAT ist.
Die Ausgangslage
Ein frischer Incus-Container läuft im managed Bridge-Netzwerk incusbr0 und bekommt eine private IP:
incus launch images:debian/12 webserver
incus list
+-----------+---------+------+-----------+
| NAME | STATE | IPV4 |
+-----------+---------+------+-----------+
| webserver | RUNNING | 10.68.100.2 |
+-----------+---------+------+-----------+
Der Container ist von außen nicht erreichbar. Die Bridge hat NAT – der Container kommt ins Internet, aber nichts kommt rein.
Proxy-Device – der Standardweg
Ein Proxy-Device leitet einen Port des Hosts an einen Dienst im Container weiter. Angelegt wird es pro Container:
incus config device add webserver webproxy proxy \
listen=tcp:0.0.0.0:8080 \
connect=tcp:127.0.0.1:80
Jetzt ist Port 8080 des Hosts auf Port 80 des Containers gemappt. curl http:// erreicht den Dienst im Container.
Wie es funktioniert
Das Proxy-Device startet einen userspace-Prozess (incus-proxy), der im Namespace des Containers läuft. Er öffnet auf dem Host einen TCP-Socket, nimmt Verbindungen an und leitet sie an die connect-Adresse innerhalb des Containers weiter.
Client ─── Host:8080 ─── incus-proxy (im Container-NS) ─── Container:80
Weil der Proxy im Container-Namespace läuft, ist 127.0.0.1 der Container – nicht der Host. Das ist der entscheidende Punkt.
Vorteile:
- Einfach, kein Kernel-Tuning, keine feste IP nötig
- Funktioniert immer, auch ohne IP-Forwarding
connect=tcp:127.0.0.1:...ist die Container-eigene Loopback-Adresse
Nachteile:
- Client-IP geht verloren (Container sieht Host/Bridge als Quelle)
- Nur TCP und UDP, aber auch Unix-Sockets möglich
Proxy mit NAT – wenn die Client-IP gebraucht wird
Mit nat=true arbeitet das Proxy-Device anders:
incus config device add webserver webproxy proxy \
listen=tcp:192.168.1.100:8080 \
connect=tcp:10.68.100.2:80 \
nat=true
Statt eines userspace-Prozesses legt Incus eine iptables-DNAT-Regel auf dem Host an. Der Kernel schreibt das Ziel der Pakete um und leitet sie direkt an den Container weiter.
Client ─── Host:8080 ─── DNAT (Kernel) ─── 10.68.100.2:80
Wichtig: Bei nat=true gelten besondere Adress-Regeln:
listenmuss eine echte IP des Hosts sein (z. B.192.168.1.100).
connectmuss die tatsächliche IP des Containers sein (z. B.
0.0.0.0 oder Wildcards sind nicht erlaubt, weil die DNAT-Regel ein konkretes Ziel benötigt. 10.68.100.2). 127.0.0.1 funktioniert nicht, weil die DNAT-Regel im Host-Namespace arbeitet – dort ist 127.0.0.1 der Host selbst.
Vorteile:
- Container sieht die Original-Client-IP (DNAT ändert nur das Ziel)
- Kein Userspace-Prozess, geringfügig weniger Overhead
Nachteile:
- Container braucht eine feste IP (sonst zeigt die DNAT-Regel ins Leere)
- Erfordert
net.ipv4.ip_forward=1 - Nur TCP und UDP
Feste IP für den Container vergeben
Für nat=true muss die Container-IP stabil sein. Incus kann das über das Device-Profil:
incus config device add webserver eth0 nic \
name=eth0 \
network=incusbr0 \
ipv4.address=10.68.100.100
Oder beim Launch:
incus launch images:debian/12 webserver
incus config device override webserver eth0 ipv4.address=10.68.100.100
incus restart webserver
Jetzt hat der Container 10.68.100.100 – und die DNAT-Regel bleibt gültig.
Der entscheidende Unterschied: Client-IP und connect-Adresse
| Aspekt | ohne NAT (nat=false) |
mit NAT (nat=true) |
|---|---|---|
| Client-IP im Container | Host/Bridge-IP | Original Client-IP |
| listen-Adresse | 0.0.0.0 oder beliebig |
Host-IP (z. B. 192.168.1.100) |
| connect-Adresse | Container-Perspektive (127.0.0.1 oder 0.0.0.0) |
Container-IP (z. B. 10.68.100.2) |
| Feste Container-IP nötig | Nein | Ja |
| ip_forward nötig | Nein | Ja |
| Namespace | Läuft im Container-NS | Läuft im Host-NS (Kernel) |
Ohne NAT (Proxy-Mode): incus-proxy läuft im Container-Namespace. Er nimmt auf dem Host Verbindungen an und leitet sie innerhalb des Containers weiter. Der Container sieht die Host/Bridge-IP als Quelle, weil der Proxy eine neue TCP-Verbindung aufbaut. Die connect-Adresse ist aus Sicht des Containers – 127.0.0.1 ist dort der Container selbst.
Mit NAT (NAT-Mode): Der Kernel schreibt per DNAT nur das Ziel-Paket um, nicht die Quelle. Der Container sieht die Original-Client-IP. Weil die Regel im Host-Namespace lebt, gelten besondere Adress-Regeln: listen braucht eine echte Host-IP (kein 0.0.0.0), connect braucht die Container-IP (nicht 127.0.0.1).
Proxy Protocol – wenn die Client-IP im Proxy-Mode gebraucht wird
Wer den einfacheren Proxy-Mode (nat=false) nutzen will, aber trotzdem die echte Client-IP braucht, kann Proxy Protocol aktivieren:
incus config device add webserver webproxy proxy \
listen=tcp:0.0.0.0:8080 \
connect=tcp:127.0.0.1:80 \
proxy_protocol=true
incus-proxy hängt dann vor jede Verbindung einen Header mit der Original-Client-IP. Die Anwendung im Container muss Proxy Protocol verstehen – Nginx, Caddy, Traefik, HAProxy können das, viele andere nicht.
Bei nat=true brauchst du Proxy Protocol nicht, weil die Client-IP ohnehin durchkommt.
Praxisbeispiele
1. Einfacher Webserver ohne NAT
incus launch images:debian/12 web
incus exec web -- apt update && apt install -y nginx
incus config device add web http proxy \
listen=tcp:0.0.0.0:80 \
connect=tcp:127.0.0.1:80
Einfach, keine feste IP nötig, Client-IP wird nicht gebraucht.
2. Webdienst mit NAT und Client-IP
# Host-IP ermitteln
ip addr show incusbr0 | grep inet
incus launch images:debian/12 app
incus exec app -- apt update && apt install -y nginx
incus config device override app eth0 ipv4.address=10.68.100.50
incus restart app
incus config device add app http proxy \
listen=tcp:192.168.1.100:8080 \
connect=tcp:10.68.100.50:80 \
nat=true
Der Dienst bekommt die echte Client-IP – nützlich für Zugriffslogs, Fail2ban oder Rate-Limiting. Container und Host brauchen feste IPs.
3. nginx mit Proxy Protocol
incus launch images:debian/12 web
incus exec web -- apt update && apt install -y nginx
In /etc/nginx/nginx.conf:
server {
listen 80 proxy_protocol;
real_ip_header proxy_protocol;
...
}
incus config device add web http proxy \
listen=tcp:0.0.0.0:80 \
connect=tcp:127.0.0.1:80 \
proxy_protocol=true
Container-IP egal, keine feste IP nötig, Client-IP kommt trotzdem an.
Persönliche Anekdote: Wie ich aus Versehen einen offenen Relay betrieb
Als ich meinen ersten Mailserver in einem Incus-Container aufgesetzt habe, war ich froh, dass das Proxy-Device so einfach funktioniert. `incus config device add ... proxy listen=tcp:0.0.0.0:25 connect=tcp:127.0.0.1:25` – fertig, Port 25 offen, Mails kommen an. Dachte ich.
Was ich zu dem Zeitpunkt nicht wusste: Der Proxy-Mode öffnet eine neue Verbindung aus dem Host-Namespace. Mein Postfix im Container sah alle Verbindungen von der Host-IP kommen. Und Postfix vertraut dem lokalen Netzwerk – warum auch nicht? In meiner main.cf stand `mynetworks = 127.0.0.0/8 10.68.100.0/24`. Die Host-IP der Bridge (10.68.100.1) war eingeschlossen.
Ergebnis: Jeder, der Port 25 an meiner Host-IP erreichte, konnte beliebig Mails über meinen Server verschicken. Ich betrieb einen offenen Relay – eine der klassischen Top-3-Misskonfigurationen im Mail-Bereich.
Die Quittung kam ein paar Tage später von meinem Provider: Eine automatische Nachricht, mein Server versende Spam. Erst war ich ungläubig – ich betreibe seit über 20 Jahren Mailserver, so etwas kann mir nicht passieren. Also Logs gecheckt. Und dann wurde mir schlecht: Hunderte Verbindungen von fremden IPs, alle von Postfix akzeptiert, weil sie von der Host-IP kamen. Ich hab sofort alles abgeschaltet, Postfix vom Netz genommen und erstmal mein ganzes Setup hinterfragt.
Die Lösung war lächerlich einfach: nat=true mit fester Container-IP setzen. Postfix sah plötzlich die echten Client-IPs, lehnte unbekannte Absender ab. Aber die Peinlichkeit sitzt bis heute tief – ich hätte mich vorher über die Unterschiede informieren sollen, statt einfach froh zu sein, dass der Port offen war.
Entscheidungsmatrix
| Szenario | Proxy ohne NAT | Proxy mit NAT |
|---|---|---|
| Einsteiger, 1–2 Dienste | ✅ | – |
| Client-IP im Log benötigt | nur mit Proxy-Protokoll | ✅ |
| Feste Container-IP vergeben vermeiden | ✅ | – |
| Keine konkrete Host-IP angeben wollen | ✅ | – |
| Einfachste Konfiguration | ✅ | – |
| Produktiv mit Zugriffslogs | – | ✅ |
Fazit
Das Proxy-Device ist der einfachste Weg, Dienste aus Incus-Containern zugänglich zu machen. Der Default (nat=false) reicht für die meisten Fälle: keine feste IP nötig, 127.0.0.1 als connect-Adresse ist der Container, Konfiguration minimal.
Wer die echte Client-IP im Container braucht, hat zwei Wege: nat=true mit fester Container-IP, oder Proxy Protocol im Proxy-Mode – dann ohne feste IP, aber die Anwendung muss es unterstützen.
