Fünfzehn Zeichen rein, acht kommen an
Innerhalb weniger Tage stellte sich bei fünf Diensten auf vier Hosts dieselbe Frage: Steht in der Konfigurationsdatei wirklich das, was im laufenden Container ankommt?
Fünfmal lautete die Antwort nein, aus vier Gründen.
Die Grundregel
Eine Konfigurationsdatei ist eine Absichtserklärung, kein Messwert. Zwischen der .env auf dem Host und der Umgebungsvariablen im Prozess liegen mehrere Übersetzungsschritte, und jeder einzelne kann den Wert verändern — meistens still.
Die Frage lautet deshalb nie „steht der Wert in der Datei", sondern „welcher Wert kommt am Ende an". Beantwortet wird sie im Container, nicht im Editor:
docker exec <container> sh -c 'echo ${#GEHEIMNIS}' # nur die Länge, kein Klartext
Der Vergleich von Länge in der Datei gegen ${#VAR} im Container ist die billigste Prüfung, die es gibt. In den ersten beiden Fällen hat sie sofort getroffen. docker inspect --format '{{range .Config.Env}}{{println .}}{{end}}' zeigt alles auf einmal — aber im Klartext, also nicht in Protokolle oder Tickets kopieren.
Fall 1: Das Dollarzeichen, das Compose auffrisst
Eine .env enthielt ein Datenbankkennwort mit 15 Zeichen. Im Container kamen 8 an.
Der Wert enthielt ein unquotiertes $, sagen wir an Position neun: abcdefgh$Qw7xyz. Docker Compose liest $Qw7xyz als Verweis auf eine Variable namens Qw7xyz, findet sie nicht und setzt eine leere Zeichenkette ein. Fünfzehn minus sieben gleich acht.
Compose sagt es sogar. Bei jedem einzelnen Aufruf:
The "Qw7xyz" variable is not set. Defaulting to a blank string.
Diese Zeile steht zwischen dem übrigen Rauschen von up -d und wird zuverlässig überlesen. In diesem Fall stand sie dort vermutlich seit der Einrichtung des Dienstes. Und auffallen konnte es nicht: Die Datenbank war mit dem gekürzten Wert initialisiert worden und lief seither damit. Alles funktionierte. Nur war das Kennwort in der Datei nicht das Kennwort der Datenbank.
Abhilfe: Werte in der .env in einfache Anführungszeichen setzen, auch wenn der aktuelle Wert harmlos aussieht. MYSQL_ROOT_PASSWORD='abc$def' unterdrückt die Ersetzung. Ob eine über env_file: eingebundene Datei genauso behandelt wird, hängt von der Compose-Version ab — docker compose config zeigt, was tatsächlich ankommt, und das ist die einzige Aussage, die zählt.
Fall 2: Der Rückfallwert, der nie auffällt
Compose-Dateien aus dem Netz schreiben Vorgaben gern so:
services:
db:
environment:
MARIADB_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-root}
SECRET_KEY: ${SECRET_KEY:-change-me-please}
Fehlt die .env oder fehlt die Variable darin, greift der Teil hinter :-. Der Dienst startet, alles funktioniert, nichts meldet sich — die Datenbank läuft mit dem Kennwort root. Bei einem der geprüften Dienste war genau das seit der Einrichtung so. Aufgefallen ist es nur, weil jemand die Compose-Datei aus einem anderen Anlass gelesen hat.
Dasselbe Muster gibt es außerhalb von Compose: getenv('X', 'vorgabe') in PHP, ${X:-…} in Shell-Skripten, X || 'default' in Node. Bei einem Kurzlink-Dienst war es der Beispiel-Cookieschlüssel aus dem eigenen, öffentlichen Quelltext des Projekts. Damit ließ sich das Anmelde-Cookie der Verwaltung fälschen — von jedem, der das Repository lesen kann. Der Dienst hatte es sogar gesagt: Über ein Jahr lang stand eine Warnung auf seiner eigenen Anmeldeseite. Wie die Compose-Zeile aus Fall 1 — gedruckt, nie gelesen.
Ein voreingestellter Wert, der funktioniert, sieht genauso aus wie ein gesetzter Wert.
Abhilfe: :- durch :? ersetzen. Dann bricht docker compose mit einer klaren Meldung ab, statt lautlos die Vorgabe zu nehmen:
MARIADB_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:?fehlt in docker/.env}
Fall 3: Das Kennwort zählt nur beim ersten Start
Der unangenehmste Teil kommt beim Reparieren.
Die offiziellen Datenbank-Images — mariadb, mysql, postgres — werten MYSQL_PASSWORD, MARIADB_ROOT_PASSWORD und Verwandte nur bei der Erstinitialisierung aus. Also nur, wenn das Datenverzeichnis leer ist.
Wer die .env korrigiert und docker compose up -d ausführt, ändert damit die Umgebung des Containers. Nicht die Datenbank. Die Anwendung meldet sich danach mit dem neuen Kennwort an und scheitert. Gesucht wird der Fehler dann bei der Quotierung — wo er nicht mehr ist.
Die Reihenfolge, die trägt:
- Sicherung fahren und prüfen, dass sie Inhalt hat.
ALTER USERin der laufenden Datenbank — für jeden betroffenen Benutzer und jede Host-Angabe, also'root'@'%'und'root'@'localhost'..envschreiben, quotiert.- Den ganzen Stack neu aufbauen, nicht nur die Datenbank. Anwendungen lesen ihre Zugangsdaten beim Start.
Vorher prüfen, ob der Healthcheck am Kennwort hängt. Wer bei MariaDB das mitgelieferte healthcheck.sh als Test eingetragen hat, ist unkritisch — es benutzt einen eigenen Benutzer healthcheck@localhost. Bei anderen Images kann ein Kennwortwechsel den Container unhealthy machen und eine Neustartschleife auslösen:
docker inspect <container> --format '{{json .Config.Healthcheck}}'
Fall 4: Das chmod, das ein Ausfall war
Eine einzelne Datei als Bind-Mount in einen Container zu hängen hat zwei Fallen, nicht eine.
Die bekanntere, hier nur als Exkurs: Docker bindet bei einer einzelnen Datei den Inode, nicht den Pfad. Wer die Datei auf dem Host bearbeitet — die meisten Editoren schreiben eine neue Datei und benennen um —, dem zeigt der Container weiterhin die alte Fassung. nginx -t und nginx -s reload melden dabei bereitwillig Erfolg, weil sie die alte Datei prüfen und laden.
Die weniger bekannte: Die Zugriffsrechte gelten für die uid im Container, nicht für die auf dem Host. Ein gut gemeintes chmod 600 auf eine gemountete Konfigurationsdatei gibt sie nur dem Host-Benutzer. Läuft der Container unter einer anderen uid, kommt er nicht mehr heran.
Beobachtet an einem Dashboard: Betriebsbenutzer auf dem Host uid 1012, Container läuft als uid 1000. Nach dem chmod 600 beantwortete das Dashboard jeden Login mit EACCES: permission denied. Auf dem Host sah ls -la danach ordentlicher aus als vorher.
Prüfung vor jedem chmod an einer gemounteten Datei:
id -u # auf dem Host
docker exec <container> id -u # im Container
docker exec <container> cat <pfad> # der eigentliche Beweis
Was für alle vier gilt
Eine Verschärfung ist eine Änderung. Rechte enger stellen, ein Kennwort setzen, eine Quotierung korrigieren — das fühlt sich nach Aufräumen an und wird deshalb nicht nachgemessen. Genau dort entstehen die Ausfälle. Ein einziger docker exec … cat oder ein Statuscode-Vergleich vorher und nachher hätte in jedem der Fälle sofort gezeigt, was los ist.
Und: die Anwendung selbst fragen, nicht nur den Statuscode. Ein Healthcheck, der 200 zählt, findet eine Warnung auf der Anmeldeseite nie. 200 ist 200, auch mit einer Warnung im Text.
