Das Problem
docker run --env-file und der Schlüssel env_file: von Docker Compose
lesen beide .env-Dateien, folgen dabei aber unterschiedlichen Regeln.
docker run übernimmt jeden Wert wörtlich: Anführungszeichen,
# Kommentare am Zeilenende und Leerzeichen am Ende werden Teil des Werts,
und Zeilen mit export oder KEY = value sind Fehler. Compose und Bash
entfernen die Anführungszeichen, verwerfen Kommentare am Zeilenende und
setzen ${VAR} ein. Eine Datei, die mit source .env und mit Compose
funktioniert, übergibt einem mit docker run gestarteten Container daher
"My App" samt Anführungszeichen.
Das Problem ist dokumentiert:
- docker/cli#3630, "Handle quotes in --env-file values consistently with Linux/WSL2 sourcing", hat 134 Reaktionen und 58 Kommentare und ist seit 2022 offen.
- Drei Pull Requests, die den Parser ändern, sind offen und nicht gemergt (#3660, #6046, #6805). Eine Änderung des Parsers würde verändern, wie bestehende Dateien gelesen werden.
- Die im Issue beschriebene Umgehung ist eine zweite Kopie jeder
.env-Datei ohne Anführungszeichen. dotenv-linter prüft den Stil. Wie jedes Werkzeug einen Wert liest, zeigt er nicht.
Wie es funktioniert
envfile-compat enthält ein
Modell jedes Lesers. Das Modell für docker run folgt den Regeln von
pkg/kvfile in docker/cli: Leerraum am Zeilenanfang wird entfernt,
#-Zeilen werden übersprungen, der Schlüssel endet am ersten =, und alles
danach ist der Wert. Eine Zeile ohne = übernimmt ihren Wert aus der
Umgebung. Das Compose-Modell folgt dem dotenv-Parser von compose-go. Es
behandelt das Präfix export, KEY: value, einfache Anführungszeichen
(wörtlich), doppelte Anführungszeichen (Escapes, Variablen, Zeilenumbrüche)
und Werte ohne Anführungszeichen mit #-Kommentaren. Verweise wie ${VAR}
und ${VAR:-default} werden wie in Compose aufgelöst, zuerst aus der
aufrufenden Umgebung, dann aus früheren Zeilen der Datei.
envfile-compat check FILE liest die Datei mit beiden Modellen und listet
jede Variable mit unterschiedlichen Werten sowie jede Zeile, die
docker run ablehnt. Der Exit-Code ist 1, sobald sich etwas unterscheidet,
die Prüfung kann also im CI laufen. Mit --bash wird die Datei zusätzlich
in einer leeren bash mit set -a eingelesen; dabei wird sie ausgeführt.
envfile-compat docker FILE gibt die Variablen so aus, wie Compose sie
liest, in der wörtlichen Form KEY=value, die docker run erwartet.
Das ist die Ausgabe für eine Datei mit drei Zeilen:
$ cat .env
APP_NAME="My App"
GREETING=hello # say hi
PORT=8080
$ envfile-compat check .env
APP_NAME
docker run '"My App"'
compose 'My App'
GREETING
docker run 'hello # say hi'
compose 'hello'
2 variable(s) differ. `envfile-compat docker .env` writes a file that
docker run --env-file reads the way Compose does.
$ envfile-compat docker .env
APP_NAME=My App
GREETING=hello
PORT=8080
Die umgewandelte Datei lässt sich direkt übergeben, zum Beispiel mit
docker run --env-file <(envfile-compat docker .env) myimage. Das Werkzeug
braucht Python 3.10 oder neuer und hat keine Abhängigkeiten.
Tests
Die Unit-Tests lassen beide Modelle über eine Korpus-Datei laufen. Sie
enthält Fälle zu Anführungszeichen, Kommentaren, Escapes und Variablen, eine
export-Zeile, KEY = value, KEY: value, eine eingerückte Zeile,
Leerzeichen am Zeilenende und einen Schlüssel ohne Wert. Weitere Tests
decken die Operatoren der Variablenersetzung ab (-, :-, +, :+,
:?), mehrzeilige Werte, nicht geschlossene Anführungszeichen, den
Bash-Leser und die Umwandlung: Die Ausgabe von envfile-compat docker muss
wieder dieselben Werte ergeben.
Eine zweite Gruppe von Tests vergleicht die Modelle mit den echten
Werkzeugen. docker compose config muss für den Korpus genau die Werte des
Compose-Modells liefern, und env in einem mit docker run --env-file
gestarteten Container genau die Werte des docker run-Modells. Auch die
umgewandelte Datei muss durch docker run unverändert gelesen werden. Ohne
Docker werden diese Tests übersprungen. Das CI führt die Tests auf
Python 3.10 bis 3.13 mit Docker Compose v2 und dem Image alpine:3.20 aus.
Grenzen
- Werte mit Zeilenumbrüchen lassen sich in dem Format, das
docker runliest, nicht ausdrücken.envfile-compat dockermeldet sie als Fehler. check --bashführt die Datei aus und ist nur für vertrauenswürdige Dateien gedacht.- Die beiden Leser sind Nachbauten. Ihre Übereinstimmung mit Docker ist für die Fälle im Testkorpus geprüft.
Code und Installationsanleitung liegen unter github.com/Jwrede/envfile-compat.