Read-only Container-Filesystem - und wo man dann doch schreiben muss

Read-only Container-Filesystem - und wo man dann doch schreiben muss

Ein Read-only-Filesystem zwingt zur expliziten Entscheidung, wo Daten persistiert werden — und reduziert nebenbei die Angriffsfläche.

Das Grundprinzip

docker run --read-only nginx

Der Container kann jetzt nichts mehr in sein Filesystem schreiben. Jeder Schreibversuch schlägt fehl:

docker run --read-only alpine sh -c 'echo test > /test'
# sh: can't create /test: Read-only file system

Warum das sinnvoll ist

  • Integrität: Schadsoftware kann keine Dateien in den Container schreiben
  • Klarheit: Wo persistente Daten landen, ist explizit konfiguriert (Volumes)
  • 12-Factor: Zwingt dazu, Logs über stdout/stderr auszugeben, nicht in Dateien
  • Debuggierbarkeit: Der Container-Zustand ist deterministisch — nur Volumes ändern sich

Das Problem: Manche Apps brauchen doch Schreibzugriff

Viele Anwendungen schreiben temporäre Dateien in /tmp, PID-Files nach /run, oder Sockets. Mit --read-only schlagen diese sofort fehl.

Lösung: tmpfs für temporäre Verzeichnisse

docker run --read-only \
  --tmpfs /tmp \
  --tmpfs /run \
  --tmpfs /var/run \
  nginx

tmpfs ist RAM-basierter Speicher — nach Container-Neustart leer, aber während der Laufzeit beschreibbar.

In Docker Compose

services:
  app:
    image: myapp
    read_only: true
    tmpfs:
      - /tmp
      - /run
    volumes:
      - app-data:/app/data    # Persistente Daten auf Volume

volumes:
  app-data:

Nginx als Beispiel

Nginx schreibt PID-Dateien und temporäre Dateien:

services:
  nginx:
    image: nginx:alpine
    read_only: true
    tmpfs:
      - /var/cache/nginx
      - /var/run
      - /tmp
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./html:/usr/share/nginx/html:ro

Was wohin gehört

| Datentyp | Ziel |
|---|---|
| Temporäre Dateien | tmpfs |
| Persistente Anwendungsdaten | Named Volume |
| Logs | stdout/stderr (kein File-Logging) |
| Konfiguration | Volume mit :ro (read-only) |
| Uploads/Dateien | Named Volume oder Objekt-Storage |

Konfigurationsdateien immer read-only mounten:

volumes:
  - ./config.yml:/app/config.yml:ro   # :ro verhindert versehentliches Überschreiben

Identifizieren was schreibt

Vor der Umstellung testen, welche Pfade die App beschreibt:

# Container starten und Datei-Zugriffe beobachten
docker run --rm -it myapp strace -e trace=openat,open sh -c './start.sh'

# Oder einfach: read-only starten und Fehler beobachten
docker run --read-only myapp 2>&1 | grep -i "read-only"

tmpfs-Optionen

tmpfs:
  - /tmp:size=100m,mode=1777   # 100MB, Sticky-Bit für /tmp
  - /run:size=10m

Read-only für Volumes

Read-only-Container mit read-only Volumes für maximale Sicherheit:

services:
  app:
    image: myapp
    read_only: true
    volumes:
      - static-files:/app/static:ro    # Nur lesen
      - app-data:/app/data             # Lesen und schreiben
    tmpfs:
      - /tmp

volumes:
  static-files:
  app-data:

Read-only-Filesysteme sind kein Aufwand für paranoia — sie sind eine saubere Architektur-Entscheidung, die Laufzeitumgebung und persistente Daten klar trennt.