Offline Container
Was wenn man ein Container braucht, dem man nicht traut und der eigentlich keinen Internet-Zugang möchte, aber er trotzdem vom Host aus kontaktierbar sein muss?
Intro
Vor kurzem bin ich auf Mailpit gestossen:
- “Mailpit - email & SMTP testing tool with API for developers”
Da ich das Tool nicht kenne, dachte ich mir:
- cool, es gibt auch ein Image
- einziges Problem: das Image könnte via Internet eine Reverse Shell öffnen
- allerdings muss ich von aussen den Container erreichen können -> darum geht ein
networks: drivers: nonenicht
So informierte ich mich wie ich am einfachsten und saubersten einem Container das Internet verweigern kann und bin auf folgendes Setup gestossen:
- mailpit mit internal network
- Proxy wie
rinetd(odersocat,caddy,nginx)
Setup
File Struktur
.
├── docker-compose.yml
└── rinetd
├── Dockerfile
└── rinetd.conf
docker-compose.yml
services:
mailpit:
image: axllent/mailpit:latest
hostname: mailpit
networks:
- internal_net
volumes:
- mailpit_data:/data
environment:
# Weist Mailpit an, diese Datei für die DB zu verwenden.
# Mails überleben nun Container-Neustarts.
- MP_DATA_FILE=/data/mailpit.db
# Optional: Setzt das "Pruning" (Löschen alter Mails) hoch
# Standard ist 1000 Mails, hier z.B. 5000
- MP_MAX_MESSAGES=5000
# necessary to support tls with self sign certs
MP_SMTP_TLS_CERT: sans:localhost
MP_SMTP_TLS_KEY: sans:localhost
restart: unless-stopped
proxy:
# Baut das Image aus unserem 'rinetd'-Ordner
build: ./rinetd
ports:
# Mappt Ports NUR auf localhost vom Host
- "127.0.0.1:8025:8025" # Host -> Proxy (Web-UI)
- "127.0.0.1:1025:1025" # Host -> Proxy (SMTP)
networks:
- default_web # Dieses Netz hat Internet (falls nötig)
- internal_net # Dieses Netz verbindet zum 'mailpit'
volumes:
# Wir mounten unsere lokale config schreibgeschützt in den Container
- ./rinetd/rinetd.conf:/etc/rinetd.conf:ro
depends_on:
- mailpit
restart: unless-stopped
networks:
# Das normale Netz mit Internetzugang
default_web:
driver: bridge
# Das "Gefängnis" ohne Internetzugang
internal_net:
driver: bridge
internal: true
volumes:
mailpit_data:
rinetd/Dockerfile
FROM debian:bookworm-slim
RUN apt-get update && \
apt-get install -y --no-install-recommends rinetd && \
# Cleanup, um die Image-Größe zu minimieren
apt-get clean && \
rm -rf /var/lib/apt/lists/*
CMD ["rinetd", "-f", "-c", "/etc/rinetd.conf"]
rinetd/rinetd.conf
# rinetd/rinetd.conf
#
# Format:
# <bind_ip> <bind_port> <forward_ip> <forward_port>
# Höre auf allen IPs IM PROXY-CONTAINER auf Port 8025
# und leite an den Container 'mailpit' auf Port 8025 weiter
0.0.0.0 8025 mailpit 8025
# Dasselbe für den SMTP-Port
0.0.0.0 1025 mailpit 1025
Verify
Wir nutzen eine IP anstatt DNS, um sicher zu sein, dass nicht nur DNS nicht geht.
docker compose up -d
# Beispiel wie der Proxy ins Internet kommt
docker exec -it proxy-1 bash
apt update
apt install wget
# 2x redirect, bevor dann die Adresse nicht aufgelöst werden kann. Aber Internet funktioniert.
# -S: Show server response
# -O FILE Save to FILE ('-' for stdout)
wget -S -O - 1.1.1.1
--2025-11-10 08:06:24-- http://1.1.1.1/
Connecting to 1.1.1.1:80... connected.
HTTP request sent, awaiting response...
HTTP/1.1 301 Moved Permanently
Server: cloudflare
Date: Mon, 10 Nov 2025 08:06:24 GMT
Content-Type: text/html
Content-Length: 167
Connection: keep-alive
Location: https://1.1.1.1/
CF-RAY: 99c40ce33c3abc41-ZRH
Location: https://1.1.1.1/ [following]
--2025-11-10 08:06:24-- https://1.1.1.1/
Connecting to 1.1.1.1:443... connected.
HTTP request sent, awaiting response...
HTTP/1.1 301 Moved Permanently
Date: Mon, 10 Nov 2025 08:06:24 GMT
Content-Length: 0
Connection: keep-alive
Report-To: {"endpoints":[{"url":"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=YOXLn8lSxj90AYYJ4H0xOj51%2BgmzoAxb%2BFCrtVYPJ66tu8snF449GVnBU%2FjO2xS6Tsao%2FbcqiilCzDNeqbPy0tSqNVepjeBsgar40g57lmRY9zA2ntlaZp0%3D"}],"group":"cf-nel","max_age":604800}
NEL: {"report_to":"cf-nel","max_age":604800}
Location: https://one.one.one.one/
Vary: Accept-Encoding
Server: cloudflare
CF-RAY: 99c40ce3e89b24bc-ZRH
Location: https://one.one.one.one/ [following]
--2025-11-10 08:06:24-- https://one.one.one.one/
Resolving one.one.one.one (one.one.one.one)... failed: Name or service not known.
wget: unable to resolve host address 'one.one.one.one'
exit
# Beispiel wie mailpit nicht mehr ins Internet kommt
docker exec -it mailpit-1 /bin/sh
# Internet geht nicht
wget -S -O - 1.1.1.1
Connecting to 1.1.1.1 (1.1.1.1:80)
wget: can't connect to remote host (1.1.1.1): Network unreachable
exit
# verify mailpit from host
echo "Yay - Mail body for offline mailpit"
curl --url 'smtp://localhost:1025' \
--mail-from 'sender@envelope.com' \
--mail-rcpt 'recipient@envelope.com' \
--upload-file email.txt