Docker Compose Env-Praezedenz: Shell, .env, environment, env_file im Ueberblick
AI generated
FROM
RUN
Docker · Compose · Konfiguration
Docker Compose Env-Praezedenz
Wer gewinnt: Shell, .env, environment oder env_file?

Vier verschiedene Quellen fuer Umgebungsvariablen, eine Reihenfolge, die selten dokumentiert wird: Wenn ein Wert im Container nicht ankommt oder ein anderer als erwarteter erscheint, steckt fast immer eine unklare Praezedenz zwischen Shell-Umgebung, .env-Datei, dem environment-Block und env_file dahinter.

17 Min. Lesezeit Compose-Interpolation .env-Datei env_file vs. environment

1. Zwei getrennte Mechanismen, ein gemeinsamer Name

Der haeufigste Denkfehler bei Docker Compose ist die Annahme, es gebe nur einen Topf mit Umgebungsvariablen. Tatsaechlich laufen zwei komplett getrennte Mechanismen parallel: die Interpolation von Platzhaltern wie ${VAR} innerhalb der compose.yaml selbst, und die Injektion von Umgebungsvariablen in den laufenden Container. Beide nutzen dieselbe Syntax und teilweise dieselben Quellen, folgen aber unterschiedlichen Regeln. Wer das nicht trennt, sucht Stunden nach einem Bug, der eigentlich ein Missverstaendnis ist.

Fuer die Interpolation liest Compose die Werte aus der Shell-Umgebung und aus einer .env-Datei im Projektverzeichnis, noch bevor die YAML-Datei ueberhaupt geparst wird. Fuer die Container-Injektion greifen zusaetzlich der environment-Block und env_file innerhalb der Service-Definition. Ein Wert kann also in der YAML-Datei interpoliert werden, ohne dass er jemals im Container ankommt, und umgekehrt kann ein Wert im Container landen, ohne dass er fuer die Interpolation sichtbar war.

2. Shell-Umgebung: die staerkste Quelle fuer Interpolation

Fuer die Interpolation von ${VAR}-Platzhaltern in der Compose-Datei hat die Shell-Umgebung, in der docker compose aufgerufen wird, die hoechste Prioritaet. Setzt man export TAG=v2 vor dem Aufruf, ueberschreibt dieser Wert automatisch jeden gleichnamigen Eintrag in der .env-Datei, egal was dort steht. Das ist beabsichtigt: CI-Pipelines und lokale Overrides sollen ohne Dateiaenderung funktionieren.

Genau das ist aber auch die haeufigste Fehlerquelle. Ein Entwickler exportiert DB_HOST in seiner .bashrc fuer ein anderes Projekt, vergisst das, und wundert sich Wochen spaeter, warum ein voellig neues Compose-Projekt eine falsche Datenbank anspricht. Der Befehl docker compose config zeigt die final aufgeloeste Konfiguration inklusive aller interpolierten Werte und ist der erste Debugging-Schritt, bevor man ueberhaupt Container startet.


# Aktive Shell-Variablen pruefen, die mit dem Projekt kollidieren koennten
env | grep -iE 'DB_|TAG|PORT'

# Final aufgeloeste Konfiguration anzeigen (nach Interpolation)
docker compose config

# Nur einen bestimmten Wert extrahieren
docker compose config | grep -A2 'DB_HOST'

3. Die .env-Datei: stiller Standardwert fuer die Interpolation

Liegt im selben Verzeichnis wie der docker compose-Aufruf eine Datei namens .env, liest Compose sie automatisch und stellt jede Zeile als Interpolationsquelle bereit, allerdings nur mit niedrigerer Prioritaet als die Shell-Umgebung. Die Syntax ist simpel: SCHLUESSEL=Wert pro Zeile, Kommentare mit #, keine Anfuehrungszeichen noetig, aber erlaubt. Wichtig: Diese Datei wird ausschliesslich von Compose selbst gelesen, nicht automatisch in den Container injiziert.

Ein zweites Missverstaendnis betrifft den Dateipfad. Compose sucht standardmaessig eine .env im aktuellen Arbeitsverzeichnis, nicht im Verzeichnis der compose.yaml, falls man diese ueber -f aus einem anderen Ordner referenziert. Seit neueren Compose-Versionen laesst sich das ueber --env-file explizit steuern, was gerade bei mehreren Umgebungen (dev, staging, prod) mit jeweils eigener Env-Datei unverzichtbar ist.


# Explizite Env-Datei fuer eine bestimmte Umgebung erzwingen
docker compose --env-file .env.staging up -d

# .env liegt woanders als die compose.yaml
docker compose -f deploy/compose.yaml --env-file deploy/.env.prod up -d

4. Der environment-Block: explizite Injektion in den Container

Der environment-Block innerhalb einer Service-Definition ist die direkteste Methode, eine Variable in den Container zu injizieren. Er kann Literalwerte enthalten oder selbst wieder interpolierte Platzhaltern wie ${DB_PASSWORD}, die dann aus Shell oder .env aufgeloest werden. Entscheidend: Werte im environment-Block ueberschreiben immer alles, was aus env_file fuer denselben Schluessel kommt, unabhaengig von der Reihenfolge im YAML-Dokument.

Eine haeufig uebersehene Variante ist die Kurzform ohne Wert, etwa nur - DB_PASSWORD ohne Gleichheitszeichen. In diesem Fall uebernimmt Compose den Wert aus der Shell-Umgebung des Host-Systems und reicht ihn unveraendert durch, was praktisch fuer Secrets ist, die nicht in einer Datei landen sollen, aber leicht vergessen wird und zu leeren Variablen im Container fuehrt, wenn die Shell den Wert gar nicht gesetzt hat.


services:
  app:
    image: myapp:latest
    environment:
      NODE_ENV: production
      DB_HOST: db
      DB_PASSWORD: ${DB_PASSWORD}   # aus Shell oder .env aufgeloest
      # Kurzform: Wert direkt aus der Host-Shell durchreichen
      - API_TOKEN

5. env_file: ganze Dateien als Variablenquelle einbinden

Waehrend der environment-Block fuer wenige, explizite Werte gedacht ist, dient env_file dem Einbinden ganzer Variablenlisten aus separaten Dateien, etwa app.env oder secrets.env. Diese Dateien werden nicht interpoliert, das heisst Platzhalter wie ${VAR} innerhalb einer per env_file geladenen Datei bleiben als Literal-String stehen und werden nicht aufgeloest, ein Unterschied, der regelmaessig zu falschen Erwartungen fuehrt.

Werden mehrere Dateien in einer Liste angegeben, gewinnt bei gleichem Schluessel die zuletzt genannte Datei. Kombiniert man env_file mit einem environment-Block, hat der environment-Block immer das letzte Wort, selbst wenn er in der YAML-Datei vor env_file steht. Diese Praezedenz ist in der Compose-Spezifikation festgelegt und nicht von der Reihenfolge der Schluessel im Dokument abhaengig.


services:
  app:
    image: myapp:latest
    env_file:
      - ./config/base.env
      - ./config/local.env   # ueberschreibt Werte aus base.env
    environment:
      # gewinnt IMMER gegenueber env_file, egal an welcher Position
      NODE_ENV: production

6. Die vollstaendige Praezedenz-Kette in der Praxis

Zusammengefasst gilt fuer die finale Variable im Container folgende Reihenfolge von hoechster zu niedrigster Prioritaet: ein im Dockerfile per ENV gesetzter Wert wird von env_file ueberschrieben, env_file wird vom environment-Block ueberschrieben, und falls die Shell beim Container-Start noch docker compose run -e VAR=wert nutzt, gewinnt dieser Wert ueber alles andere. Fuer die reine YAML-Interpolation gilt separat: Shell-Umgebung schlaegt .env-Datei.

In der Praxis lohnt sich eine feste Konvention pro Team: Secrets und geheime Werte kommen ausschliesslich per env_file aus einer nicht versionierten Datei, feste Konfigurationswerte wie Portnummern stehen direkt im environment-Block, und projektweite Defaults wie die Compose-Projektbezeichnung liegen in der .env-Datei. Diese Trennung reduziert die Zahl der Stellen, an denen ein Wert ueberschrieben werden kann, drastisch.


# Einzelnen Wert fuer einen einmaligen Lauf ueberschreiben
docker compose run -e DEBUG=true app npm test

# Effektive Umgebung eines laufenden Containers pruefen
docker compose exec app env | sort

7. Zusatzfaktor: mehrere Compose-Dateien mit -f

Wird ein Projekt mit mehreren -f-Flags gestartet, etwa docker compose -f compose.yaml -f compose.override.yaml up, gilt fuer environment-Bloecke: Die spaeter angegebene Datei ueberschreibt gleichnamige Schluessel aus der frueheren. Fuer Listen wie env_file werden die Eintraege dagegen zusammengefuehrt (gemerged), nicht ersetzt, sofern nicht explizit ein leeres Array die Liste zuruecksetzt. Diese asymmetrische Merge-Logik zwischen Skalar- und Listenfeldern ist eine der subtilsten Fallen in mehrschichtigen Compose-Setups.

Ein compose.override.yaml wird von Compose automatisch mitgeladen, wenn es im selben Verzeichnis wie die Haupt-Compose-Datei liegt, ganz ohne explizites -f. Das ist praktisch fuer lokale Entwickler-Overrides, kann aber zu Verwirrung fuehren, wenn ein Teammitglied nicht weiss, dass diese Datei existiert und automatisch greift. Ein kurzer Blick mit docker compose config deckt solche versteckten Overrides zuverlaessig auf.

8. Systematisches Debugging bei falschen Werten

Kommt ein unerwarteter Wert im Container an, hilft ein dreistufiges Vorgehen: Erstens docker compose config ausfuehren, um die nach Interpolation aufgeloeste YAML-Struktur zu sehen. Zweitens pruefen, ob eine .env-Datei oder Shell-Variable existiert, die den Platzhalter unerwartet fuellt. Drittens im laufenden Container selbst mit docker compose exec app env die tatsaechlich injizierten Variablen inspizieren, denn Interpolationsergebnis und Container-Umgebung sind zwei verschiedene Dinge.

Ein haeufiger Sonderfall ist eine leere statt einer fehlenden Variable. Wird ${VAR} interpoliert und ist VAR weder in der Shell noch in .env gesetzt, ersetzt Compose den Platzhalter standardmaessig durch einen leeren String, ohne Fehler oder Warnung. Erst mit der Syntax ${VAR:?Fehlermeldung} bricht Compose den Start kontrolliert ab, was fuer produktionskritische Variablen wie Datenbank-Zugangsdaten dringend empfohlen ist.


services:
  app:
    environment:
      # Bricht den Start mit klarer Fehlermeldung ab, falls nicht gesetzt
      DB_PASSWORD: ${DB_PASSWORD:?DB_PASSWORD muss gesetzt sein}
      # Fallback-Wert, falls die Variable fehlt
      LOG_LEVEL: ${LOG_LEVEL:-info}

9. Empfehlungen fuer stabile Compose-Konfigurationen

Eine .env.example-Datei ohne echte Werte, dafuer aber mit allen erwarteten Schluesseln, im Repository zu versionieren, macht fuer neue Teammitglieder sofort sichtbar, welche Variablen ueberhaupt existieren. Die echte .env gehoert in die .gitignore, ebenso jede Datei, die per env_file echte Secrets enthaelt. Diese Trennung von Struktur (versioniert) und Werten (nicht versioniert) verhindert die haeufigste Ursache fuer versehentlich committete Zugangsdaten.

Fuer mehrere Umgebungen empfiehlt sich ein konsistentes Namensschema wie .env.dev, .env.staging, .env.prod, kombiniert mit dem expliziten --env-file-Flag statt sich auf implizites Auto-Loading zu verlassen. Wer zusaetzlich in der CI-Pipeline pruefen laesst, ob alle in der Compose-Datei referenzierten Variablen tatsaechlich gesetzt sind, etwa per docker compose config --quiet mit Fehlerauswertung, fängt fehlende Werte ab, bevor sie in Produktion zu leeren Strings werden.

Quelle Wirkt auf Prioritaet Interpoliert Platzhalter?
docker compose run -e Container-Umgebung Hoechste Ja, direkt uebergeben
environment-Block Container-Umgebung Hoch, schlaegt env_file Ja, aus Shell/.env
Shell-Umgebung (export) YAML-Interpolation Hoch, schlaegt .env N/A, ist die Quelle
env_file Container-Umgebung Niedrig, unter environment Nein, Werte bleiben Literal
.env-Datei YAML-Interpolation Niedrig, unter Shell N/A, ist die Quelle
Dockerfile ENV Container-Umgebung (Default) Niedrigste Nein

Mironsoft

Container-Infrastruktur, CI-Pipelines und Deployment-Automatisierung

Docker-Setups, die im Team und in Produktion tragfähig bleiben?

Wir prüfen bestehende Dockerfiles und Compose-Stacks auf Sicherheitslücken, aufgeblähte Images und fragile Build-Pipelines und bauen daraus eine Container-Infrastruktur, die schnell baut, sicher läuft und im Team nachvollziehbar bleibt.

Dockerfile-Review

Multi-Stage-Builds, Layer-Caching und Image-Größe systematisch optimieren.

Security-Audit

Container-Isolation, Secrets-Handling und Image-Scanning gegen echte Angriffsflächen absichern.

CI/CD-Integration

Build-Pipelines, Registries und Deployment-Strategien für reproduzierbare Releases aufbauen.

10. Zusammenfassung

Compose-Env-Praezedenz: Das Wichtigste auf einen Blick

Interpolation

Shell-Umgebung schlaegt .env-Datei, beide wirken nur auf ${VAR}-Platzhalter in der YAML.

Container-Injektion

environment-Block schlaegt env_file, unabhaengig von der Reihenfolge im Dokument.

Mehrere -f-Dateien

Skalarwerte werden ueberschrieben, Listen wie env_file werden zusammengefuehrt.

Debugging

docker compose config zeigt die Interpolation, docker compose exec app env die echte Container-Umgebung.

11. FAQ: Compose-Env-Praezedenz: Das Wichtigste auf einen Blick

1Warum kommt meine Variable aus der .env-Datei nicht im Container an?
Die .env-Datei wird nur fuer die Interpolation von Platzhaltern in der compose.yaml gelesen, nicht automatisch in den Container injiziert. Damit ein Wert im Container ankommt, muss er zusaetzlich im environment-Block referenziert oder ueber env_file eingebunden werden.
2Was gewinnt, wenn dieselbe Variable in environment und env_file steht?
Der environment-Block gewinnt immer, unabhaengig davon, an welcher Position er in der YAML-Datei steht. Diese Praezedenz ist in der Compose-Spezifikation fest definiert.
3Werden Platzhalter in einer per env_file eingebundenen Datei aufgeloest?
Nein. Dateien, die per env_file geladen werden, werden nicht interpoliert. Ein Eintrag wie DB_URL=${HOST} bleibt als Literal-String mit dem Dollarzeichen erhalten und wird nicht ersetzt.
4Wo sucht Compose standardmaessig nach der .env-Datei?
Im aktuellen Arbeitsverzeichnis des docker compose-Aufrufs, nicht zwingend im Verzeichnis der referenzierten compose.yaml. Bei abweichenden Pfaden hilft das explizite --env-file-Flag.
5Was passiert, wenn eine referenzierte Variable nirgends gesetzt ist?
Compose ersetzt den Platzhalter standardmaessig durch einen leeren String, ohne Fehler oder Warnung auszugeben. Mit der Syntax ${VAR:?Meldung} kann man stattdessen einen kontrollierten Abbruch mit Fehlermeldung erzwingen.
6Kann ich mehrere env_file-Eintraege gleichzeitig angeben?
Ja, env_file akzeptiert eine Liste von Dateipfaden. Bei gleichen Schluesseln gewinnt die zuletzt in der Liste genannte Datei, die Dateien werden also nacheinander gemerged.
7Warum sehe ich in docker compose config andere Werte als im Container?
docker compose config zeigt nur das Ergebnis der YAML-Interpolation, also Shell und .env-Datei. Die tatsaechliche Container-Umgebung kann durch environment und env_file zusaetzlich veraendert werden, deshalb sollte man mit docker compose exec app env pruefen.
8Wird compose.override.yaml automatisch geladen?
Ja, sofern sie im selben Verzeichnis wie die Haupt-Compose-Datei liegt, wird sie ohne explizites -f-Flag automatisch mitgeladen und ihre Werte ueberschreiben gleichnamige Schluessel aus der Hauptdatei.
9Wie uebergebe ich einen Wert nur fuer einen einzelnen Lauf, ohne eine Datei zu aendern?
Mit docker compose run -e VAR=wert servicename oder docker compose run --env VAR=wert. Dieser Wert hat die hoechste Prioritaet und ueberschreibt alle anderen Quellen fuer diesen einen Aufruf.
10Sollte die .env-Datei versioniert werden?
Nein, die echte .env mit produktiven Werten gehoert in die .gitignore. Stattdessen sollte eine .env.example mit denselben Schluesseln aber Platzhalterwerten versioniert werden, damit neue Teammitglieder wissen, welche Variablen benoetigt werden.