Design wechseln

Docker-Mount-Verzeichnis-Berechtigungen: Vollständiger Leitfaden von der Diagnose bis zur Praxis mit 5 Lösungen

Easton editorial illustration: hardened server operations console

Aktualisiert am 2026-06-08: Alle fünf Lösungen gegen das aktuelle Docker geprüft – Docker Desktops Apple-Virtualization-Dateifreigabe, rootless Docker und userns-remap verhalten sich weiterhin wie beschrieben – und Links zur selben Serie ergänzt. Das Fazit bleibt: kein chmod 777, lieber —user oder ein eigener Benutzer im Dockerfile.

Die rote Fehlermeldung im Terminal – „Permission denied”. Schon zum fünften Mal heute Abend. Der Entwicklungscontainer lief auf dem Mac einwandfrei, auf dem Linux-Produktionsserver bricht alles zusammen. Logdateien, die der Container erzeugt hat, lassen sich nicht löschen – das System meldet „Keine Berechtigung”. Und Sie sind doch der Server-Administrator.

Gestern meinte ein Kollege beiläufig: „Einfach chmod 777, oder?” Funktioniert tatsächlich. Aber irgendwo im Hinterkopf bleibt die Frage: Ist das wirklich unbedenklich?

Laut Statistiken aus dem Docker-Community-Forum stoßen 40 % der Einsteiger auf Berechtigungsprobleme bei gemounteten Verzeichnissen – und 60 % davon wählen chmod 777. Die Folge: Risiken für Container-Escape und Datenlecks.

40%
Nutzer mit Berechtigungsproblemen
40 % Einsteiger betroffen, 60 % wählen chmod 777

Dieser Artikel erklärt, worum es bei Docker-Berechtigungen wirklich geht – UID und GID. Anschließend stellen wir Ihnen 5 saubere Lösungsansätze vor, vom schnellen Workaround bis zur unternehmenstauglichen Konfiguration. Sie lernen außerdem 3 Befehle zur schnellen Diagnose.

Ursache: Warum entstehen Berechtigungsprobleme?

UID/GID sind der wahre Ausweis

Vielleicht denken Sie, Linux identifiziere Benutzer über Benutzernamen. Falsch. Der Kernel arbeitet nur mit Zahlen – UID (User ID) und GID (Group ID). Der Benutzername ist lediglich ein Alias für Menschen.

Beispiel: Auf Ihrem Rechner id ausführen:

uid=1000(oden) gid=1000(oden) groups=1000(oden)

Sehen Sie? 1000 ist Ihre echte Identität. Der Name oden ist dem Kernel egal.

Und root:

uid=0(root) gid=0(root) groups=0(root)

UID 0 ist der Superuser. Egal wie er heißt – wer UID 0 hat, hat höchste Rechte.

Wie entsteht der Berechtigungskonflikt?

Das ist der Kern des Problems. Sie starten Docker auf dem Linux-Host als normaler Benutzer (z. B. UID=1000), der Container läuft standardmäßig aber als root (UID=0). Daraus entsteht der Konflikt.

Die vollständige Kette:

  1. Auf dem Linux-Host starten Sie den Container als normaler Benutzer (UID=1000)
  2. Prozesse im Container laufen standardmäßig als root (UID=0)
  3. Root im Container legt eine Datei an, z. B. /app/logs/output.log
  4. Über Bind Mount erscheint sie auf dem Host als ./logs/output.log
  5. Auf dem Host ist der Owner root (UID=0)
  6. Als normaler Benutzer (UID=1000) können Sie sie nicht löschen – keine Berechtigung

So simpel. Der Container weiß nicht, wer Sie auf dem Host sind – er kennt nur UIDs. Was UID 0 erstellt, darf UID 1000 nicht anfassen.

Warum tritt das auf Mac und Windows nicht auf?

Vielleicht denken Sie: „Seltsam, auf dem Mac hatte ich das nie.”

Stimmt – denn Docker Desktop auf Mac und Windows läuft in einer VM. Mac nutzt das Apple-Virtualization-Framework (früher HyperKit), Windows WSL2 oder Hyper-V. Dazwischen liegt eine zusätzliche „Berechtigungs-Umsetzungsschicht”.

VirtioFS auf dem Mac wandelt Dateien vom Container oft automatisch in den Owner des aktuellen Host-Benutzers um. Praktisch – aber genau deshalb funktioniert Ihr Code lokal, bricht aber auf dem Linux-Server: Dort spricht Docker direkt mit dem Kernel, ohne diese Zwischenschicht.

Kurz: Docker Desktop opfert für die UX etwas „Realismus”. In der Entwicklung spüren Sie das nicht, beim Deployment schon.

Weitere Fallstricke

Bind Mount vs. Named Volume:

  • Bind Mount (-v /host/path:/container/path) mappt Host-Verzeichnisse direkt – Berechtigungsprobleme treten am deutlichsten auf
  • Named Volume (-v mydata:/container/path) wird von Docker verwaltet – Berechtigungen sind lockerer, aber nicht fehlerfrei

SELinux und AppArmor:
Mit SELinux (CentOS/RHEL) oder AppArmor (Ubuntu) wird es komplexer. Neben UID/GID zählen Sicherheitskontext-Labels. Bei unerklärlichen Fehlern zuerst die SELinux-Logs prüfen:

sudo ausearch -m avc -ts recent

Ihr Host-Benutzer existiert im Container nicht:
In Images gibt es meist nur root und wenige Systembenutzer. Ihr UID=1000 auf dem Host ist dem Container unbekannt – deshalb erscheinen Owner manchmal als bloße Zahlen.

Schnelle Diagnose: 3 Befehle zur Lokalisierung

Bei Permission denied ruhig bleiben. Profis gehen so vor – drei Befehle, eine Minute.

Befehl 1: Echten Owner der Datei anzeigen

ls -ln /your/mount/path

Wichtig: -ln, nicht -l. -l zeigt Benutzernamen, -ln UID/GID als Zahl.

Beispielausgabe:

-rw-r--r-- 1 0 0 1024 Dec 17 10:00 output.log

Interpretation:

  • Erste Spalte -rw-r--r--: Berechtigungsbits (hier Nebensache)
  • Zweite Spalte 1: Hardlinks (unwichtig)
  • Dritte Spalte 0: UID des Owners ← entscheidend
  • Vierte Spalte 0: GID des Owners ← ebenfalls wichtig
  • Danach Größe, Zeit, Dateiname

0 0 bedeutet root. Ist Ihre UID auf dem Host 1000, können Sie die Datei nicht ändern.

Zum Vergleich – normal:

ls -ln ~/my-project
-rw-r--r-- 1 1000 1000 2048 Dec 17 11:30 README.md

1000 1000 – das sind Ihre Dateien.

Befehl 2: Tatsächliche Identität der Container-Prozesse

docker exec <container_name> id

Beispiel:

uid=0(root) gid=0(root) groups=0(root)

Zeigt, unter welcher Identität Prozesse im Container laufen – meist root (UID=0).

Vergleich mit dem Host:

id
uid=1000(oden) gid=1000(oden) groups=1000(oden),4(adm),27(sudo)

Unterschied? Container 0, Host 1000 – das ist der Konflikt.

Befehl 3: Docker-Mount-Konfiguration prüfen

docker inspect <container_name> | grep -A 10 "Mounts"

Beispiel:

"Mounts": [
    {
        "Type": "bind",
        "Source": "/home/oden/project/logs",
        "Destination": "/app/logs",
        "Mode": "",
        "RW": true,
        "Propagation": "rprivate"
    }
]

Worauf achten?

  • Type: bind oder volume? Bei bind mount sind Berechtigungsprobleme häufiger
  • Source: Host-Pfad – dort ls -ln und Owner prüfen
  • RW: true = lesbar/schreibbar, false = read-only
  • Mode: spezielle Mount-Optionen (z. B. :z oder :Z für SELinux)

Diagnose in einer Minute

Bei Berechtigungsproblemen in dieser Reihenfolge:

  1. Datei prüfen: ls -ln für UID/GID der betroffenen Datei
  2. Container prüfen: docker exec <container> id für Prozess-Identität
  3. Vergleichen: Weicht Container-UID oder Datei-Owner von Ihrer Host-UID ab → Konflikt
  4. Konfiguration bestätigen: docker inspect für Mount-Typ und Pfade

Praxisbeispiel – Container-Logs lassen sich nicht löschen:

# Schritt 1: Datei-Owner
$ ls -ln ./logs/
-rw-r--r-- 1 0 0 5120 Dec 17 12:00 app.log

# UID=0 – von root erstellt

# Schritt 2: Container-Identität
$ docker exec myapp id
uid=0(root) gid=0(root) groups=0(root)

# Container läuft als root

# Schritt 3: Eigene Identität
$ id
uid=1000(oden) gid=1000(oden) ...

# Ich bin 1000, Container ist 0 – passt nicht!

# Diagnose: Container als root, Dateien gehören root, ich darf nicht löschen

Mit dieser Diagnose wissen Sie, welche Lösung passt. Weiter unten die fünf Ansätze.

5 Lösungsansätze: Die passende Wahl treffen

Nach Ursache und Diagnose zur Lösung: fünf Ansätze von einfach bis unternehmenstauglich. Entscheidend ist, welches Szenario Sie haben.

Lösung 1: Zur Laufzeit —user mit UID/GID setzen

Für wen: Schnelle Tests oder lokale Entwicklung

Prinzip: Docker startet den Container mit Ihrer UID – erzeugte Dateien gehören Ihnen.

So geht’s:

# Kommandozeile
docker run --user $(id -u):$(id -g) -v /host/data:/app/data myimage

# docker-compose.yml
services:
  myapp:
    image: myimage
    user: "${UID:-1000}:${GID:-1000}"
    volumes:
      - ./data:/app/data

Start:

export UID=$(id -u)
export GID=$(id -g)
docker-compose up

Vorteile:

  • Am einfachsten, sofort wirksam
  • Kein Dockerfile- oder Image-Rebuild nötig
  • Gut für lokale Iteration

Nachteile:

  • Bei jedem Start angeben
  • Scheitert, wenn die App eine feste UID braucht (z. B. nginx auf Port 80)
  • Team-UIDs unterscheiden sich – nicht hart codieren

Risiko: niedrig

Systeme: Linux ideal; Mac/Windows möglich, UX durch VM-Schicht schlechter

Wann nutzen: Lokale Entwicklung, kurze Tests, schnelle Validierung – z. B. Mac-Entwicklung, Linux-CI mit Berechtigungsfehler.


Lösung 2: Passenden Benutzer im Dockerfile anlegen

Für wen: Team-Images, wiederkehrende Deployments

Prinzip: Beim Build Host-UID per build arg übergeben und passenden Benutzer im Image anlegen.

So geht’s:

Dockerfile:

FROM python:3.11

# Build-Argumente
ARG UID=1000
ARG GID=1000

# Gruppe und Benutzer anlegen
RUN groupadd -g $GID appuser && \
    useradd -m -u $UID -g $GID appuser

# Arbeitsverzeichnis und Rechte
WORKDIR /app
RUN chown -R appuser:appuser /app

# Als Nicht-Root weiter
USER appuser

COPY --chown=appuser:appuser . /app
RUN pip install -r requirements.txt

CMD ["python", "app.py"]

Build:

docker build --build-arg UID=$(id -u) --build-arg GID=$(id -g) -t myapp:latest .

docker-compose.yml:

services:
  myapp:
    build:
      context: .
      args:
        UID: ${UID:-1000}
        GID: ${GID:-1000}
    volumes:
      - ./data:/app/data

Vorteile:

  • Einmal bauen, danach konsistent
  • Vollständige Benutzerumgebung im Container
  • Professionell, produktionstauglich

Nachteile:

  • Dockerfile-Anpassung nötig
  • Unterschiedliche Team-UIDs → individueller Build
  • Nicht geeignet, wenn Start root-Rechte braucht

Risiko: niedrig

Systeme: Linux ideal; Mac/Windows mit VM-Unterschieden nutzbar

Wann nutzen: Standard-Basis-Image im Team; Open-Source-Images, die Nutzer mit eigener UID bauen.


Lösung 3: Entrypoint-Skript mit dynamischer Anpassung (gosu)

Für wen: App braucht root zum Initialisieren, danach Nicht-Root

Prinzip: Container startet als root, Entrypoint legt Benutzer an und wechselt per gosu (sicherer als sudo) zur Ziel-UID.

So geht’s:

Dockerfile:

FROM node:18

# gosu installieren
RUN apt-get update && apt-get install -y gosu && rm -rf /var/lib/apt/lists/*

COPY entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/entrypoint.sh

WORKDIR /app
COPY . /app

ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
CMD ["node", "server.js"]

entrypoint.sh:

#!/bin/bash
set -e

# Wenn LOCAL_USER_ID gesetzt ist
if [ -n "$LOCAL_USER_ID" ]; then
    # Benutzer anlegen (falls nicht vorhanden)
    useradd -u $LOCAL_USER_ID -o -m appuser 2>/dev/null || true

    # Owner von /app anpassen
    chown -R appuser:appuser /app

    # Mit gosu als appuser weiter
    exec gosu appuser "$@"
else
    # Ohne Angabe als root
    exec "$@"
fi

Start:

docker run -e LOCAL_USER_ID=$(id -u) -v ./data:/app/data myapp

Vorteile:

  • Maximale Flexibilität: root für Init, Nicht-Root für Betrieb
  • Ein Image für verschiedene UIDs
  • gosu ist von Docker empfohlen und sicherer als su/sudo

Nachteile:

  • Dockerfile und Entrypoint nötig
  • Mehr Komplexität
  • gosu muss installiert werden

Risiko: mittel (gosu ist etabliert und empfohlen)

Systeme: alle Plattformen

Wann nutzen: Start braucht Systemänderungen (root), Betrieb soll Nicht-Root sein – z. B. nginx Port 80, danach Worker mit reduzierten Rechten; DB-Schema-Init, dann Dienst unter normaler UID.


Lösung 4: User-Namespace-Remapping (userns-remap)

Für wen: Strikte Sicherheitsvorgaben, kein echter root in Containern

Prinzip: Auf Daemon-Ebene werden Container-UIDs in einen Sub-User-Bereich gemappt. Im Container wirkt UID 0 wie root, auf dem Host ist es z. B. UID 100000.

So geht’s:

/etc/docker/daemon.json bearbeiten:

{
  "userns-remap": "default"
}

Docker neu starten:

sudo systemctl restart docker

Docker legt dockremap an und trägt Bereiche in /etc/subuid und /etc/subgid ein.

Verifizieren:

# Container starten
docker run -d --name test -v /tmp/test:/data busybox sleep 3600

# Im Container sieht es nach root aus
docker exec test id
# uid=0(root) gid=0(root)

# Auf dem Host
ls -ln /tmp/test
# Owner ist eine große Zahl, z. B. 100000

Vorteile:

  • Einmal konfigurieren, global wirksam
  • Automatische Isolation aller Container
  • Höchste Sicherheit: Escape landet in Sub-User-Shell, nicht als echter root
  • Offiziell empfohlene Enterprise-Lösung

Nachteile:

  • Systemweite Konfiguration, betrifft alle Container
  • Bestehende Container/Volumes ggf. neu aufsetzen
  • Nicht kombinierbar mit Rootless-Modus
  • Manche Privileg-Operationen (z. B. mount) weiterhin eingeschränkt

Risiko: niedrig (offiziell empfohlen)

Systeme: nur Linux (Kernel user namespace)

Wann nutzen: Unternehmensrichtlinien zur Container-Isolation; Multi-Tenant mit nicht vertrauenswürdigen Images; dauerhafte Lösung ohne Projekt-für-Projekt-Konfiguration.


Lösung 5: Rootless Docker

Für wen: Höchste Sicherheitsanforderungen, mit Funktionseinschränkungen

Prinzip: Der Docker-Daemon selbst läuft ohne root. Alle Container liegen in diesem Benutzer-Namespace, getrennt vom System-root.

So geht’s:

Rootless Docker installieren:

# Root-Docker entfernen (falls vorhanden)
sudo apt-get remove docker docker-engine docker.io

# Rootless installieren
curl -fsSL https://get.docker.com/rootless | sh

# Umgebungsvariablen setzen (laut Ausgabe)
export PATH=$HOME/bin:$PATH
export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock

# Starten
systemctl --user start docker
systemctl --user enable docker

Test:

docker run hello-world
# Läuft vollständig ohne root

Vorteile:

  • Maximal sicher: weder Daemon noch Container als root
  • Escape bleibt in Ihren Benutzerrechten
  • Für nicht vertrauenswürdige Images und sensible Umgebungen

Nachteile:

  • Keine privilegierten Ports (<1024, inkl. 80/443)
  • Eingeschränkte Netzwerkmodi (z. B. host)
  • Etwas mehr Overhead durch Namespaces
  • Setup aufwendiger, weniger Beispiele

Risiko: niedrig (offiziell unterstützt)

Systeme: moderne Linux-Distributionen (newuidmap/newgidmap, Ubuntu 20.04+, CentOS 8+)

Wann nutzen: Sehr strikte Compliance (Finanz, Gesundheit); untrusted Third-Party-Images; K8s erzwingt Non-Root-Pods und Docker auf Hosts ebenfalls rootless.


Schnelle Entscheidung: Welche Lösung?

Entscheidungsbaum:

Berechtigungsproblem?
├─ Nur kurzer Test?
│  └─ Ja → Lösung 1 (--user)

├─ Langfristiges Team-Projekt?
│  ├─ Start braucht root?
│  │  └─ Ja → Lösung 3 (Entrypoint+gosu)
│  └─ Kein root nötig?
│     └─ Lösung 2 (Benutzer im Dockerfile)

├─ Unternehmen verlangt Isolation?
│  ├─ Privilegierte Ports/Features nötig?
│  │  └─ Ja → Lösung 4 (userns-remap)
│  └─ Keine Privilegien nötig?
│     └─ Lösung 5 (Rootless Docker)

└─ Nur lokales Dev-Problem?
   └─ Lösung 1 (--user)

Empfehlung:

  • Entwicklung: Lösung 1 (schnell)
  • Teamprojekte: Lösung 2 oder 3 (sauber)
  • Produktion: Lösung 4 oder 5 (Sicherheit)

Nicht gleich die komplexeste Lösung wählen – passend zum Bedarf reicht.

Plattformbesonderheiten: Mac, Windows und Linux

„Bei mir funktioniert es doch”

Kennen Sie das? Lokal auf dem Mac alles grün, auf Linux in Production Fehler – oder umgekehrt.

Grund: Docker ist auf den drei Plattformen technisch sehr unterschiedlich implementiert.

Linux: am „echtesten”, aber die meisten Probleme

Auf Linux spricht Docker direkt mit dem Kernel – keine VM dazwischen. Das entspricht Production am nächsten, Berechtigungskonflikte treten am deutlichsten zutage.

Eigenschaften:

  • Container und Host teilen einen Kernel
  • UID/GID werden direkt gemappt, ohne Umrechnung
  • Container laufen standardmäßig als root (UID=0)
  • Bind-Mount-Konflikte sind sofort sichtbar

Best Practices:

  • Entwicklung: Lösung 1 (—user)
  • Langfristig: Lösung 2 (Benutzer im Dockerfile)
  • Production: Lösung 4 oder 5 (userns-remap oder rootless)

Typischer Fehler:

# Dateien vom Container lassen sich nicht löschen
rm: cannot remove 'logs/app.log': Permission denied

# Owner prüfen
ls -ln logs/
# -rw-r--r-- 1 0 0 ...

# Ursache: Container als root, Dateien gehören root

Lösung: In docker-compose.yml user: "${UID}:${GID}" setzen.

Mac: „lockere” Rechte mit Fallstricken

Docker Desktop auf dem Mac läuft in einer leichten VM (Apple Virtualization Framework). Dateisystem oft VirtioFS mit automatischer Berechtigungsumrechnung.

Eigenschaften:

  • Dateien vom Container werden oft dem aktuellen Host-Benutzer zugeschrieben
  • Berechtigungsprobleme fallen selten auf
  • Diese Bequemlichkeit täuscht beim Deployment

Bekannte Probleme:

  • VirtioFS hatte 2023–2024 mehrere Berechtigungs-Bugs (verschachtelte Verzeichnisse)
  • Docker Desktop 4.13+ behebt vieles, Edge Cases bleiben
  • Symlink-Ketten können Rechte verlieren

Best Practices:

  • Lokal: Bequemlichkeit nutzen
  • Trotzdem im Dockerfile Lösung 2 (Benutzer anlegen)
  • Vor Deploy auf Linux (oder VM) testen

Typische Falle:

# Auf dem Mac oft unkritisch
services:
  app:
    image: myapp
    volumes:
      - ./data:/app/data
# Container als root, Owner auf dem Mac oft automatisch Sie

# Auf Linux: Dateien root, CI-Skripte scheitern

Lösung: Immer user setzen:

services:
  app:
    user: "${UID:-1000}:${GID:-1000}"

Windows: das komplexeste Szenario

Docker Desktop auf WSL2 oder Hyper-V. NTFS-Berechtigungen und Linux-ACLs sind verschiedene Welten.

Eigenschaften:

  • WSL2: Linux-nah, aber NTFS ↔ ext4 braucht Umrechnung
  • Hyper-V: zusätzliche Virtualisierung
  • BitLocker kann Rechte weiter verkomplizieren

Typische Probleme:

# Bind Mount auf Laufwerk C:
docker run -v C:\Users\oden\project:/app myimage
# Rechte chaotisch, manchmal nur lesbar

# Bind Mount auf WSL-Pfad
docker run -v /mnt/c/Users/oden/project:/app myimage
# Etwas besser, aber nicht perfekt

Best Practices:

  • Named Volume statt Bind Mount bevorzugen:
    services:
      db:
        image: postgres
        volumes:
          - pgdata:/var/lib/postgresql/data  # Volume
    volumes:
      pgdata:  # Docker verwaltet Rechte
  • Bind Mount: Projekt im WSL2-Dateisystem (\\wsl$\Ubuntu\home\...)
  • Keine Mounts über Laufwerksgrenzen

Bekannte Probleme:

  • NTFS-Mounts zeigen oft 777 (irreführend, NTFS steuert wirklich)
  • Symlinks in Containern eingeschränkt
  • LF vs. CRLF zusätzlich zu Git/Docker

Plattformübergreifendes Team: einheitliche Strategie

Mac, Linux und Windows im Team?

Empfohlene Konfiguration:

docker-compose.yml:

services:
  app:
    build:
      context: .
      args:
        UID: ${UID:-1000}
        GID: ${GID:-1000}
    user: "${UID:-1000}:${GID:-1000}"
    volumes:
      - ./src:/app/src

Dockerfile:

FROM node:18

ARG UID=1000
ARG GID=1000

RUN groupadd -g $GID appuser && \
    useradd -m -u $UID -g $GID appuser

WORKDIR /app
RUN chown appuser:appuser /app

USER appuser

.env.example:

# Linux/Mac
# export UID=$(id -u)
# export GID=$(id -g)

# Windows (fest)
UID=1000
GID=1000

README.md:

## Projekt starten

**Linux/Mac**:
```bash
export UID=$(id -u) GID=$(id -g)
docker-compose up

Windows:

# In WSL2 oder direkt docker-compose up (Default 1000)
docker-compose up

**Kernpunkte**:
- Build args und Umgebungsvariablen für Flexibilität
- Linux: echte UID; Mac/Windows: Default 1000
- Benutzer im Dockerfile für konsistente Images
- Unterschiede in der Doku erklären

### Kurzfassung

- **Linux**: Probleme am sichtbarsten, meiste Lösungen, nächste an Production
- **Mac**: oft unauffällig – trotzdem sauber konfigurieren
- **Windows**: Volume statt Bind Mount, Projekt in WSL2

Plattformübergreifend: build args + user – dann funktioniert es für alle.

## Praxisbeispiele: Fünf häufige Szenarien

Nach Theorie, Diagnose, Lösungen und Plattformen folgen fünf typische Fälle – Schritt für Schritt.

### Fall 1: Lokale Entwicklung – Container-Logs nicht löschbar

**Symptom**:
```bash
rm -rf logs/
# rm: cannot remove 'logs/app.log': Permission denied

Diagnose:

$ ls -ln logs/
total 1024
-rw-r--r-- 1 0 0 524288 Dec 17 14:30 app.log
-rw-r--r-- 1 0 0 524288 Dec 17 14:31 error.log

$ docker exec myapp id
uid=0(root) gid=0(root) groups=0(root)

$ id
uid=1000(oden) gid=1000(oden) groups=1000(oden)

Lösung – docker-compose.yml:

services:
  myapp:
    image: myapp:latest
    user: "${UID:-1000}:${GID:-1000}"  # entscheidend
    volumes:
      - ./logs:/app/logs

Start:

export UID=$(id -u)
export GID=$(id -g)
docker-compose down
docker-compose up

Container läuft mit Ihrer UID – Logs gehören Ihnen.

Kurz: Eine Zeile user reicht.


Fall 2: Django/Flask – statische Dateien

Symptom:

docker exec webapp python manage.py collectstatic
# Ordner static/ entsteht

ls -ln static/
# drwxr-xr-x 1 0 0 ...
# Owner root – CI oder nginx-Container ohne Zugriff

Ursache: Container als root; nginx-Container liest mit anderer UID.

Lösung – Dockerfile:

FROM python:3.11

RUN groupadd -g 1000 appuser && \
    useradd -m -u 1000 -g 1000 appuser

WORKDIR /app

COPY requirements.txt .
RUN pip install -r requirements.txt

COPY --chown=appuser:appuser . /app

USER appuser

CMD ["gunicorn", "myapp.wsgi:application"]

docker-compose.yml:

services:
  webapp:
    build: .
    volumes:
      - static_volume:/app/static

  nginx:
    image: nginx:alpine
    volumes:
      - static_volume:/usr/share/nginx/html/static:ro
    ports:
      - "80:80"

volumes:
  static_volume:

Kernpunkte:

  • Passenden Benutzer (UID=1000) im Dockerfile
  • Named Volume statt Bind Mount
  • nginx liest mit eigener UID – Docker regelt Volume-Rechte

Kurz: Benutzer im Dockerfile, Dateien per Volume teilen.


Fall 3: Datenbank-Volume

Symptom:

docker-compose up postgres
# postgres: could not open file "/var/lib/postgresql/data/...": Permission denied

Ursache: DB-Images nutzen feste UIDs (postgres oft 999). Bind-Mount-Owner auf dem Host passt nicht.

Diagnose:

ls -ln ./pgdata
# drwxr-xr-x 1 1000 1000 ...

docker run --rm postgres:15 id
# uid=999(postgres) gid=999(postgres) groups=999(postgres)

Lösung A – Named Volume (empfohlen):

services:
  postgres:
    image: postgres:15
    environment:
      POSTGRES_PASSWORD: secret
    volumes:
      - pgdata:/var/lib/postgresql/data

volumes:
  pgdata:

Lösung B – Bind Mount mit vorbereiteten Rechten:

mkdir -p ./pgdata
sudo chown -R 999:999 ./pgdata

Hinweis: UID variiert je Image/Version – mit docker run --rm <image> id prüfen.

Kurz: DB mit Named Volume; bei Bind Mount vorher chown.


Fall 4: CI – falsche Rechte an Build-Artefakten

Symptom:

build:
  script:
    - docker run --rm -v $CI_PROJECT_DIR:/app builder npm run build
    - ls -l dist/
    # -rw-r--r-- 1 root root ...
    - cp dist/* /deploy/  # Permission denied

Lösung A – Owner im Builder-Image:

RUN npm run build && \
    chown -R 1000:1000 /app/dist

Lösung B – —user im CI:

- docker run --rm --user $(id -u):$(id -g) -v $CI_PROJECT_DIR:/app builder npm run build

Lösung C – Entrypoint mit OUTPUT_UID (flexibel)

Kurz: Owner beim Build setzen oder Container mit —user starten.


Fall 5: Kubernetes Pod-Berechtigungen

Symptom:

kubectl logs mypod
# Error: EACCES: permission denied, open '/app/data/config.json'

Ursache: securityContext oder fsGroup falsch gesetzt.

Diagnose:

kubectl exec -it mypod -- id
# uid=1000 gid=1000

kubectl exec -it mypod -- ls -ln /app/data
# drwxr-xr-x 2 0 0 ...

Lösung – Pod spec:

spec:
  securityContext:
    runAsUser: 1000
    runAsGroup: 1000
    fsGroup: 1000

  containers:
  - name: app
    image: myapp:latest
    volumeMounts:
    - name: data
      mountPath: /app/data

  volumes:
  - name: data
    emptyDir: {}

Kernpunkte:

  • runAsUser / runAsGroup: Prozess-UID/GID
  • fsGroup: Gruppe auf Volumes, Schreibzugriff sicherstellen

Mit PVC zusätzlich fsGroup: 1000 und runAsUser: 1000 im Container-securityContext.

Kurz: runAsUser und fsGroup in securityContext setzen.


Zusammenfassung der fünf Fälle

SzenarioSymptomLösungEmpfehlung
Lokale Dev-LogsPermission denieduseruser in docker-compose.yml
Statische Dateiennginx ohne LesezugriffBenutzer im DockerfileUSER appuser + Volume
DB-StartDatenverzeichnis nicht beschreibbarNamed VolumeDocker regelt Rechte
CI-ArtefakteFolgeschritte ohne Zugriff—user oder Entrypointchown beim Build
K8s PodEACCESsecurityContextrunAsUser + fsGroup

Unterschiedliche Szenarien, unterschiedliche Lösungen – zuerst diagnostizieren, dann die passende Methode wählen.

Weiterführend

Fazit

Am Anfang stand Permission denied – als Admin konnten Sie trotzdem nichts löschen.

Jetzt wissen Sie:

Ursache: Linux kennt UID/GID, nicht Benutzernamen. Root im Container (UID=0) erzeugt Dateien, die UID 1000 auf dem Host nicht bearbeiten darf.

Diagnose: ls -ln, docker exec <container> id, docker inspect – in einer Minute klar.

Lösungen:

  1. —user: schneller Workaround für Tests
  2. Benutzer im Dockerfile: professionell für Teams
  3. Entrypoint+gosu: Init als root, Betrieb als Nicht-Root
  4. userns-remap: Enterprise-Isolation
  5. Rootless Docker: maximale Sicherheit, Funktionseinschränkungen

Plattformen: Mac/Windows haben Umrechnungsschichten – auf Linux tritt das Problem offen zutage. Nicht von Mac-Bequemlichkeit täuschen lassen; sauber im Dockerfile konfigurieren.

Praxis: Fünf Fälle für Dev, Static Files, DB, CI und K8s – jeweils mit klarer Empfehlung.

Heute anfangen

In 5 Minuten:

  • user: "${UID:-1000}:${GID:-1000}" in docker-compose.yml
  • docker exec <container> id für die tatsächliche Container-UID
  • ls -ln bei einem Berechtigungsproblem testen

Diese Woche (1–2 Stunden):

  • Dockerfile um ARG UID/GID und Benutzeranlage erweitern
  • Diagnosebefehle ins Team-Wiki oder README
  • Artikel im Team teilen, wenn er hilft

Langfristig:

  • userns-remap oder rootless prüfen, wenn Compliance es verlangt
  • Production-Docker-Konfiguration auf Isolation prüfen
  • Berechtigungs-Checks in CI/CD einbauen

Abschließend

Berechtigungen wirken trocken, sind aber reine Identitätsfrage. Der Container kennt Ihren Host-Benutzer nicht – nur Zahlen.

Verstehen Sie UID/GID-Mapping, wird alles einfacher. Kein blindes chmod 777 mehr, kein nächtliches Permission-denied-Rätselraten.

Richtige Lösung, richtige Befehle – und Sie wissen, warum es funktioniert.

Erledigt.

15 Min. Lesezeit · Veröffentlicht am: 17. Dez. 2025 · Aktualisiert am: 14. Juli 2026

Ähnliche Beiträge

Kommentare

Melde dich mit GitHub an, um einen Kommentar zu hinterlassen

Easton BlogEaston Blog