Docker Compose Secrets im Vergleich zu .env
Wo environment:, env_file: und secrets: in Docker Compose ein Credential jeweils offenlegen, warum secrets: keinen Swarm braucht, und das .env + .gitignore-Muster, das dieses Homelab tatsaechlich verwendet.

Ich habe ein frisches Postgres-Passwort direkt unter environment: in ein neues Compose-File eingefügt und dann mit dem Cursor über git add . innegehalten. Dieses Passwort wäre gleich im Klartext in einem Repo gelandet, das ich auf eine selbst gehostete Gitea-Instanz pushe — nicht weil ich unvorsichtig bin, sondern weil environment: der Weg des geringsten Widerstands ist und Compose Sie nie warnt.
TL;DR
environment:schreibt den Wert in die Umgebung des Containers und in die Ausgabe vondocker inspect, in beiden Fällen im Klartext.env_file:hält den Wert aus dem Compose-File selbst heraus, landet aber trotzdem indocker inspect, sobald der Container läuft.- Der Top-Level-Block
secrets:ist in Compose v2 nicht auf Swarm beschränkt — er funktioniert eigenständig und mountet den Wert als Datei, die der Container liest, nie als Umgebungsvariable. secrets:ist nicht ohne Neustart aktualisierbar und braucht eine dateibasierte Quelle — für ein einzelnes Homelab ist das echte Zusatzarbeit, kein Standard.- Das tatsächliche Muster dieser Seite:
.env.exampleim Git eingecheckt,.envin.gitignore,env_file:in jedem Compose-Dienst.
KEY-STAT: 65 % — Anteil der Container-Secret-Leaks, die GitGuardians "State of Secrets Sprawl"-Report 2025 allein auf ENV-basierte Offenlegung zurückführt
Die drei Wege, wie Compose einem Container ein Secret gibt
environment: setzt die Variable direkt im Compose-File, entweder als literaler Wert oder als ${VAR}, interpoliert aus der Shell oder einer von Compose automatisch geladenen .env-Datei. Woher auch immer der Wert stammt — er landet in der gerenderten Konfiguration des Compose-Files und in der Umgebung des laufenden Containers, sichtbar für alles, was docker inspect auf diesem Container ausführen kann, und für jeden Prozess darin, der seine eigene Umgebung ausliest.
env_file: verweist auf eine separate Datei (meist .env), statt Werte im Compose-File zu schreiben. Das Compose-File selbst bleibt frei von literalen Secrets — genau deshalb existiert diese Option. Sobald der Container aber startet, werden diese Werte auf demselben Weg als Umgebungsvariablen injiziert wie bei environment: — docker inspect zeigt sie also weiterhin, und jeder Code im Container, der seine Umgebung ausgibt (ein Stacktrace, ein Debug-Endpoint, eine kompromittierte Abhängigkeit), legt sie ebenfalls offen.
secrets: unterscheidet sich grundsätzlich, nicht nur in der Verpackung. Deklarieren Sie ein Secret unter dem Top-Level-Schlüssel secrets: des Compose-Files, gestützt auf eine Datei oder einen externen Secret-Store, und referenzieren Sie es dann in der eigenen secrets:-Liste eines Dienstes. Docker mountet es als schreibgeschützte Datei unter /run/secrets/<name> im Container — nie als Umgebungsvariable, taucht also nicht in docker inspect, Prozesslisten oder einem versehentlichen Umgebungs-Dump auf.
Wo jede Variante tatsächlich leakt
environment: | env_file: | secrets: | |
|---|---|---|---|
Sichtbar in docker inspect | Ja | Ja | Nein |
| Sichtbar in Image-Layern (falls zur Build-Zeit eingebacken) | Ja, bei Verwendung in einem Dockerfile-ENV | Ja, falls zur Build-Zeit hineinkopiert | Nein — nur zur Laufzeit gemountet |
Risiko eines versehentlichen git add | Hoch — Wert steckt im Compose-File | Gering — Wert liegt in einer separaten, gitignoreten Datei | Gering — wie env_file, die Quelldatei ist gitignored |
| Sichtbar im eigenen Umgebungs-Dump des Containers | Ja | Ja | Nein — es ist eine Datei, keine Variable |
| Ohne Container-Neustart aktualisierbar | Nein | Nein | Nein |
Wann sich secrets: auch ohne Swarm lohnt
Der häufigste Mythos über secrets: ist, dass es Swarm-Modus voraussetzt. Das stimmt nicht — Compose v2 unterstützt den Top-Level-Schlüssel secrets: bei einem ganz normalen docker compose up, gestützt auf eine lokale Datei statt auf den von Swarm verwalteten Secret-Store. Das bedeutet: Eine einzelne Proxmox-VM mit Compose kann echte dateibasierte Secrets nutzen, nicht nur Swarm-Cluster.
Der Haken ist echte Zusatzarbeit für echte Sicherheit: Ein secrets:-Wert muss bereits als Datei auf der Festplatte existieren (oder eingespeist werden), er ist nicht ohne Neustart aktualisierbar — ändern Sie die Datei, müssen Sie den Container trotzdem neu starten, damit er sie übernimmt —, und die Anwendung muss so geschrieben oder konfiguriert sein, dass sie einen Dateipfad statt einer Umgebungsvariable liest, was nicht jedes fertige Container-Image von Haus aus unterstützt. Für ein Homelab mit ein paar selbst gehosteten Apps, deren Bedrohungsmodell im Wesentlichen "das darf nicht in Git oder einen Screenshot leaken" lautet, lohnt sich diese Zusatzarbeit oft nicht. Für alles mit einem Credential mit echtem Schadensradius — ein Datenbank-Superuser-Passwort, ein API-Key mit Abrechnungszugriff — lohnt sie sich.
Warnung
Weder environment: noch env_file: schützt ein Secret vor jemandem mit docker inspect-Zugriff auf den Host, einschließlich jedem, der docker-compose.yml lesen kann, nachdem ein Wert einmal in ein Log oder eine CI-Ausgabe interpoliert wurde. Behandeln Sie beide als "hält es aus Git heraus", nicht als "hält es vor dem Host geheim".
Das .env + .gitignore-Muster, das diese Seite tatsächlich verwendet
Jeder Compose-Stack auf diesem Homelab folgt derselben Konvention: Eine .env.example-Datei wird mit jedem Schlüsselnamen und einem Platzhalterwert ins Repo eingecheckt, .env selbst steht in .gitignore und wird nie committet, und der Compose-Block jedes Dienstes verwendet env_file: .env statt inline environment:-Werte. Das Repo auf einen neuen Host zu klonen bedeutet, .env.example einmal nach .env zu kopieren und echte Werte einzutragen — das Compose-File selbst muss nie bearbeitet werden, und es gibt nichts, was versehentlich committet werden könnte, weil die echten Werte nie eine getrackte Datei berühren.
Tipp
Fügen Sie eine Pre-Commit-Prüfung hinzu (auch ein einzeiliges grep in einem Git-Hook nach .env in git diff --cached --name-only), falls Sie schon einmal versehentlich eine .env-Datei gestaged haben — es passiert einmal, und danach nie wieder, sobald der Hook existiert.
Dieses Muster ist env_file:, nicht secrets: — es löst das Git-Leak-Problem, das tatsächliche Alltagsrisiko für ein Homelab, ohne die Datei-Mounting-Zusatzarbeit, die secrets: verlangt. Der Docker-Compose-Networking-Guide und der Guide zu Volumes und Backups behandeln die beiden anderen Pro-Dienst-Entscheidungen, die genauso bewusst getroffen werden — Storage und Netzwerk werden pro Dienst gewählt, nicht projektweit vorgegeben, und die Secrets-Handhabung folgt derselben Logik.
Entscheidungstabelle: was für welches Credential?
| Szenario | Empfehlung |
|---|---|
| API-Key oder DB-Passwort auf einem privaten Homelab, Hauptrisiko ist Git-Exposition | env_file: mit gitignoretem .env |
Credential mit echtem Schadensradius (DB-Superuser, abrechnungsrelevanter API-Key) auf einem Host, den andere per docker inspect einsehen können | secrets:, dateibasiert, auch ohne Swarm |
| Wert, der sich oft ändert und ohnehin einen Container-Neustart braucht | env_file: — die fehlende Hot-Reload-Fähigkeit von secrets: kostet hier nichts zusätzlich |
| Selbst gehosteter Git-Server, der das Repo hält (siehe der Vergleich Gitea vs. Forgejo vs. GitLab CE) | Beide — entscheidend ist, dass der Wert dieses Repo von vornherein nie erreicht |
Literaler Wert inline in environment: für irgendetwas Sensibles | Nicht tun — die einzige Option, die garantiert im Compose-File selbst landet |
Fazit
environment: und env_file: landen beide in docker inspect; der Unterschied zwischen beiden ist nur, ob der Wert jemals eine Datei berührt, die Sie committen könnten. secrets: ist die einzige Option, die ein Credential vollständig aus der Umgebung des Containers heraushält, und sie funktioniert auf einem einzelnen Compose-Host auch ohne Swarm — aber ihre Anforderungen an Datei-Mounting und fehlendes Hot-Reload machen sie nur dort sinnvoll, wo der Schadensradius eines Credentials diese Zusatzarbeit rechtfertigt. Für die meisten Homelab-API-Keys und DB-Passwörter schließt eine disziplinierte .env + .gitignore-Konvention das Risiko, das im Alltag tatsächlich zählt.
Häufige Fragen
Ist die Verwendung von .env-Dateien in Produktion sicher?
Sie hält Werte aus dem Compose-File und der Git-Historie heraus, was den häufigsten Leak-Weg schließt. Sie verbirgt die Werte aber nicht vor allem, was docker inspect-Zugriff auf den Host hat — für Credentials mit hohem Schadensradius verwenden Sie stattdessen den Top-Level-Block secrets:.
Braucht Docker Compose secrets den Swarm-Modus?
Nein. Compose v2 unterstützt den Top-Level-Schlüssel secrets:, gestützt auf eine lokale Datei, bei einem ganz normalen docker compose up — ohne einem Swarm beizutreten oder ihn zu initialisieren.
Was ist der Unterschied zwischen env_file und environment in Docker Compose?
environment: schreibt Werte direkt ins Compose-File; env_file: verweist stattdessen auf eine separate Datei. Beide landen als Umgebungsvariablen im laufenden Container und beide erscheinen in docker inspect — der Unterschied liegt nur darin, ob der Wert versehentlich in einer getrackten Compose-Datei landen kann.
Wie halte ich API-Keys aus Docker-Compose-Dateien heraus?
Verwenden Sie env_file:, das auf eine in .gitignore gelistete .env-Datei zeigt, und checken Sie eine .env.example mit Platzhalterwerten ein, damit die echten Keys nie eine getrackte Datei berühren. Für Credentials mit ernstem Schadensradius verwenden Sie stattdessen den Block secrets:, der den Wert überhaupt nie als Umgebungsvariable offenlegt.
Mehr aus Self-Hosting & Privatsphäre

Was docker compose up, --build und build --no-cache jeweils wirklich neu bauen, die Volume-Falle, die einen frisch gebauten Container trotzdem veraltet aussehen laesst, und echte Rebuild-Zeiten aus diesem Homelab.

Ich habe docker compose stop ausgefuehrt und erwartet, dass der ganze Stack verschwindet. Container, Netzwerk und Volumes blieben trotzdem bestehen -- was stop, down, down -v und kill wirklich abbauen, und wann welcher Befehl der richtige ist.

Jede Self-Hosting-Liste ist eine ungeprüfte Aufzählung ohne ehrliches 'lohnt sich nicht'. Sechs Kategorien tatsächlich im Homelab getestet – KI-Chatbot, Musik, Automatisierung, Reddit, Git und CI/CD – mit echten Hardwarekosten und einer Kategorie, die sich für die meisten Menschen nicht lohnt.
Bleiben Sie auf dem Laufenden
Erhalten Sie die neuesten Artikel direkt in Ihr Postfach. Kein Spam, jederzeit abbestellbar.
Docker Compose: Build, Pull oder --no-cache
Was docker compose up, --build und build --no-cache jeweils wirklich neu bauen, die Volume-Falle, die einen frisch gebauten Container trotzdem veraltet aussehen laesst, und echte Rebuild-Zeiten aus diesem Homelab.
Weiterlesen