Zum Inhalt springen
myvpsguide

Passwörter in Containern: was wohin gehört

Warum Zugangsdaten in Umgebungsvariablen sichtbarer sind als gedacht, wie Docker Secrets und Dateien statt Variablen funktionieren und wie ein versehentlich veröffentlichtes Passwort sauber ersetzt wird.

veröffentlicht 19.08.2026
4 min lesezeit
fortgeschritten

Fast jede Container-Anleitung im Netz legt Zugangsdaten als Umgebungsvariable an. Das funktioniert und ist für ein Testsystem in Ordnung. Für alles andere lohnt es, einmal nachzusehen, wer diese Variablen eigentlich lesen kann.

Wer Umgebungsvariablen sieht#

# Der ganze Umgebungsblock, im Klartext, ohne Root-Rechte im Container:
docker inspect meinapp | jq '.[0].Config.Env'

# Auch aus jedem anderen Prozess im selben Container:
docker compose exec app env | grep -i pass

Damit ist klar, wo die Daten überall auftauchen:

OrtWer liest mit
docker inspectjeder mit Docker-Zugriff, also faktisch Root
Prozessumgebungjeder Prozess im Container, auch fremder Code
Abstürze und FehlerberichteBibliotheken, die die Umgebung mitschicken
compose.yaml im Repositoryalle, die das Repository sehen, auch später noch
Shell-Historiewer den Server hat

Der vierte Punkt ist der teuerste: Ein Passwort, das einmal in einem Commit steht, bleibt in der Historie, auch wenn die Datei später geändert wird.

Stufe 1: raus aus der Compose-Datei#

Der kleinste sinnvolle Schritt. Werte in eine .env-Datei neben der Compose-Datei, die nicht ins Repository geht:

# .env
POSTGRES_PASSWORD=…
SMTP_PASSWORT=…
chmod 600 .env
echo ".env" >> .gitignore
git check-ignore -v .env      # gegenprüfen, dass es wirklich greift
services:
  db:
    image: postgres:17
    environment:
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?fehlt}

Das :?fehlt ist kein Zierrat: Ohne den Wert bricht Compose mit einer klaren Meldung ab, statt einen Container mit leerem Passwort zu starten.

Stufe 2: Dateien statt Variablen#

Viele Abbilder unterstützen …_FILE-Varianten. Dann liest die Anwendung den Wert aus einer Datei, und er steht nirgends in der Umgebung:

services:
  db:
    image: postgres:17
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/db_passwort
    secrets:
      - db_passwort

secrets:
  db_passwort:
    file: ./geheim/db_passwort.txt
mkdir -p geheim && chmod 700 geheim
head -c 24 /dev/urandom | base64 > geheim/db_passwort.txt
chmod 600 geheim/db_passwort.txt
echo "geheim/" >> .gitignore
# Gegenprobe: die Umgebung ist jetzt sauber
docker inspect $(docker compose ps -q db) | jq '.[0].Config.Env' | grep -i pass
Warum das ohne Swarm funktioniert

Der secrets:-Block gilt oft als Swarm-Sache. In Compose funktioniert die Variante mit file: auch ohne Cluster, die Datei wird als /run/secrets/<name> eingehängt, schreibgeschützt und nur für diesen Container. Der Unterschied zum einfachen Volume ist gering, aber die Absicht steht in der Datei, und das ist bei Zugangsdaten die halbe Miete.

Stufe 3: gar nicht auf dem Server#

Für Dinge, die wirklich weh tun, Zahlungsschlüssel, Signaturschlüssel, Zugänge zu fremden Systemen, ist die richtige Antwort ein Speicher außerhalb der Anwendung: ein Tresor, aus dem beim Start geholt wird, mit kurzlebigen Zugangsdaten. Das ist deutlich mehr Aufwand und lohnt erst, wenn mehrere Systeme dieselben Geheimnisse brauchen.

Für einen einzelnen VPS ist Stufe 2 plus ein ordentlicher Passwortmanager für die Menschen (Vaultwarden) der realistische Stand.

Die Regeln, die unabhängig von der Stufe gelten#

Ein Geheimnis pro Zweck. Dasselbe Passwort für Datenbank und Mailversand bedeutet: Ein Fund kompromittiert beides.

Rechte prüfen, nicht annehmen.

find . -name ".env" -o -name "*.env" | xargs ls -l
# 600, und der Eigentümer ist der, der die Container startet — nicht "alle".

Nichts in die Protokolle. Anwendungen, die ihre Konfiguration beim Start ausgeben, schreiben das Passwort ins Journal, wo es Wochen liegen bleibt:

docker compose logs | grep -iE "password|secret|token" | head

Keine Zugangsdaten ins Abbild. Ein COPY .env /app/ im Dockerfile brennt das Geheimnis in eine Schicht, die man auch später noch auslesen kann, selbst wenn eine spätere Schicht die Datei löscht.

docker history --no-trunc meinabbild | grep -i env

Wenn etwas veröffentlicht wurde#

Löschen reicht nicht, tauschen schon

Ein Zugangsdatum, das in einem Repository, einem Screenshot, einer Protokollzeile oder einem Ticket gelandet ist, gilt als kompromittiert, auch wenn der Commit später korrigiert wird. Automatisierte Sucher finden veröffentlichte Schlüssel in Minuten. Der einzige wirksame Schritt ist der Austausch, in dieser Reihenfolge:

  1. Neu erzeugen bei der ausstellenden Stelle (Datenbank-Benutzer, API-Anbieter, Bot-Portal).
  2. Überall eintragen, wo der alte Wert steht.
  3. Alten Wert ungültig machen, nicht nur ersetzen.
  4. Nachsehen, ob er benutzt wurde, Zugriffsprotokolle des betroffenen Dienstes.
  5. Erst dann die Historie aufräumen, falls möglich.

Häufige Fragen#

Reicht es, .env in .gitignore zu setzen?#

Für die Zukunft ja, für die Vergangenheit nein. git log -p -- .env zeigt, ob der Wert schon einmal drin war.

Sind Umgebungsvariablen immer falsch?#

Nein, für Einstellungen ohne Geheimniswert sind sie genau richtig. Die Trennlinie verläuft zwischen Konfiguration und Zugangsdaten.

Was ist mit docker run -e PASS=…?#

Landet zusätzlich in der Shell-Historie und in der Prozessliste des Hosts. Für einen einmaligen Test hinnehmbar, sonst nicht.

Wie oft sollte man Zugangsdaten wechseln?#

Nach jedem Verdacht sofort, und beim Ausscheiden von Personen, die sie kannten. Ein starres Ablaufdatum ohne Anlass bringt wenig und führt meist zu schwächeren Passwörtern.

Und in Kubernetes?#

Dort heißt der Mechanismus ähnlich, ist aber standardmäßig nur base64-kodiert, nicht verschlüsselt. Die Denkweise aus Stufe 3 gilt dort früher.

Kurz gesagt#

  • docker inspect zeigt jede Umgebungsvariable, auch die mit dem Passwort.
  • Stufe 1: .env mit 600, in .gitignore, mit ${VAR:?fehlt}.
  • Stufe 2: …_FILE plus secrets:, dann steht nichts mehr in der Umgebung.
  • Nie ins Abbild, nie ins Protokoll, ein Geheimnis pro Zweck.
  • Veröffentlicht heißt tauschen, nicht löschen.
MRMedia

Geschrieben aus dem laufenden Betrieb: MRMedia betreibt eigene Proxmox- Hosts, einen Backup-Server und Kundendienste in der EU. Fehler in einer Anleitung sind Fehler im eigenen Betrieb. Korrekturen bitte über Discord.