Umgebungsvariablen in Docker meistern: Konfiguration vs. Geheimnisse
Erschließen Sie sichere und flexible Docker-Bereitstellungen, indem Sie Umgebungsvariablen meistern. Dieser umfassende Leitfaden verdeutlicht den entscheidenden Unterschied zwischen der Verwendung von Umgebungsvariablen für die allgemeine Anwendungskonfiguration und der sicheren Verwaltung sensibler Daten wie API-Schlüssel und Passwörter. Lernen Sie praktische Methoden zum Übergeben nicht sensibler Einstellungen kennen, verstehen Sie die schwerwiegenden Risiken der Offenlegung von Geheimnissen über Umgebungsvariablen und entdecken Sie, wie Sie Docker Secrets und Compose für eine robuste, verschlüsselte Geheimnisverwaltung nutzen können. Erweitern Sie Ihr Docker-Wissen und schützen Sie Ihre Anwendungen.
Umgebungsvariablen in Docker meistern: Konfiguration vs. Geheimnisse
Umgebungsvariablen sind in Docker praktisch, da sie es ermöglichen, dass dasselbe Image in Entwicklung, Staging und Produktion mit unterschiedlichen Einstellungen ausgeführt wird. Diese Bequemlichkeit wird riskant, wenn Teams Passwörter, Signierschlüssel und API-Token in denselben Topf werfen wie Log-Level und Portnummern.
Das saubere mentale Modell ist einfach: Umgebungsvariablen sind für nicht sensible Laufzeitkonfigurationen in Ordnung. Geheimnisse sollten aus einem Geheimnisspeicher oder einer eingehängten Geheimnisdatei stammen, mit eingeschränktem Zugriff und einem Rotationsplan.
Umgebungsvariablen für die Konfiguration verstehen
Umgebungsvariablen sind eine unkomplizierte und weit verbreitete Methode, um Laufzeitkonfigurationen an Anwendungen zu übergeben, einschließlich solcher, die in Docker-Containern ausgeführt werden. Sie ermöglichen es, das Verhalten einer Anwendung zu ändern, ohne das Docker-Image neu zu erstellen, was Ihre Container flexibler und portabler macht. Dies ist ideal für nicht sensible, dynamische Einstellungen wie Anwendungsportnummern, Debug-Flags oder URLs von Drittanbieterdiensten.
Methoden zum Übergeben von Konfigurationsvariablen
Docker bietet mehrere Möglichkeiten, Umgebungsvariablen in Ihren Containern zu definieren und zu injizieren:
1. ENV-Anweisung im Dockerfile
Die ENV-Anweisung setzt eine Standard-Umgebungsvariable, die beim Ausführen des Containers verfügbar ist. Dies ist geeignet für Variablen, die sich wahrscheinlich nicht ändern, oder um sinnvolle Standardwerte für Ihre Anwendung bereitzustellen.
FROM alpine:latest
ENV APP_PORT=8080
ENV DEBUG_MODE=false
COPY ./app /app
WORKDIR /app
CMD ["/app/start.sh"]
Tipp: Während ENV Standardwerte setzt, können diese zur Laufzeit überschrieben werden.
2. -e- oder --env-Flag mit docker run
Beim Starten eines einzelnen Containers können Sie das -e- oder --env-Flag verwenden, um Umgebungsvariablen direkt zu übergeben. Dies ist üblich für Ad-hoc-Tests oder um spezifische Einstellungen bereitzustellen, die von den Dockerfile-Standards abweichen.
docker run -d -p 80:8080 --name my_app_instance \
-e APP_PORT=80 \
-e DEBUG_MODE=true \
my_app_image:latest
3. env_file in Docker Compose
Für die Verwaltung mehrerer Umgebungsvariablen, insbesondere über mehrere Dienste hinweg, die in einer docker-compose.yml-Datei definiert sind, ist die Option env_file sehr praktisch. Sie ermöglicht das Laden von Variablen aus einer oder mehreren .env-Dateien und hält Ihre docker-compose.yml sauberer.
docker-compose.yml:
version: '3.8'
services:
webapp:
image: my_app_image:latest
ports:
- "80:8080"
env_file:
- ./config/app.env
./config/app.env:
APP_PORT=8080
DEBUG_MODE=false
API_ENDPOINT=https://api.example.com/v1
4. environment-Schlüssel in Docker Compose
Alternativ können Sie Umgebungsvariablen direkt im Abschnitt environment eines Dienstes in docker-compose.yml definieren. Dies wird oft für eine kleine Anzahl von Variablen oder für Variablen bevorzugt, die für einen einzelnen Dienst spezifisch sind.
version: '3.8'
services:
webapp:
image: my_app_image:latest
ports:
- "80:8080"
environment:
APP_PORT: 8080
DEBUG_MODE: false
Die Fallstricke bei der Verwendung von Umgebungsvariablen für Geheimnisse
Während Umgebungsvariablen hervorragend für die Konfiguration geeignet sind, sind sie grundsätzlich nicht sicher für die Verwaltung sensibler Daten (Geheimnisse) wie Datenbankpasswörter, API-Schlüssel oder private SSH-Schlüssel. Dies ist eine kritische Sicherheitslücke, die oft übersehen wird, insbesondere in Entwicklungsumgebungen.
Warum Umgebungsvariablen für Geheimnisse unsicher sind:
Sichtbarkeit über
docker inspect: Jeder mit Zugriff auf den Docker-Host kann die Umgebungsvariablen eines laufenden Containers mitdocker inspect <container_id>leicht einsehen. Das bedeutet, dass Ihre Geheimnisse im Klartext sichtbar sind.# Beispiel für die Offenlegung eines Geheimnisses (TUN SIE DAS NICHT IN DER PRODUKTION) docker run -d -e DB_PASSWORD=mysecretpassword --name insecure_app nginx:latest # Jeder kann das Passwort sehen docker inspect insecure_app | grep DB_PASSWORDProzessausspähung: Innerhalb des Containers könnten andere Prozesse oder Benutzer (falls mehrere Benutzer vorhanden sind) in der Lage sein, Umgebungsvariablen zu lesen, insbesondere wenn die Anwendung als Root oder mit erhöhten Rechten läuft.
Protokollierung und Verlauf: Umgebungsvariablen können versehentlich in Protokollen, CI/CD-Pipeline-Verläufen oder Shell-Verläufen landen, was zu einer versehentlichen Offenlegung führt.
Image-Ebenen: Wenn Sie
ENVin einem Dockerfile mit einem Geheimnis verwenden, wird dieses Geheimnis in eine Image-Ebene eingebrannt und bleibt dort, selbst wenn Sie versuchen, es in einer späteren Ebene mitunsetzu entfernen. Dadurch kann das Geheimnis aus dem Image selbst abgerufen werden.Versehentliches Teilen:
.env-Dateien oderdocker-compose.yml-Dateien, die Geheimnisse enthalten, werden oft in Versionskontrollsysteme eingecheckt oder unangemessen geteilt, was zu einer weit verbreiteten Offenlegung führt.
Warnung: Die Behandlung sensibler Informationen als normale Umgebungsvariablen ist ein häufiger Sicherheitsfehler. Gehen Sie immer davon aus, dass Umgebungsvariablen auf dem Host und im Container öffentlich sichtbar sind.
Sichere Verwaltung von Geheimnissen in Docker
Um die Sicherheitsmängel von Umgebungsvariablen für sensible Daten zu beheben, bietet Docker dedizierte Funktionen zur Geheimnisverwaltung, hauptsächlich über Docker Secrets (für Docker Swarm) und externe Tools wie Docker Compose mit secrets-Funktionalität (die Docker Swarm-Geheimnisse nutzen oder einfach Dateien einhängen kann).
Docker Secrets (Docker Swarm-Modus)
Docker Secrets ist eine Funktion, die in den Docker Swarm-Modus integriert ist und eine sichere Möglichkeit bietet, sensible Daten für Dienste zu übertragen und zu speichern. Geheimnisse sind:
- Verschlüsselt im Ruhezustand in den Raft-Protokollen des Swarm-Managers.
- Sicher übertragen an autorisierte Dienstaufgaben.
- Als In-Memory-Dateien im Dateisystem des Containers eingehängt, typischerweise unter
/run/secrets/<secret_name>, anstatt als Umgebungsvariablen bereitgestellt. - Nur zugänglich für Dienste, denen explizit Zugriff gewährt wurde.
So verwenden Sie Docker Secrets (Swarm-Modus)
- Swarm initialisieren (falls noch nicht geschehen):
docker swarm init ```
- Ein Geheimnis erstellen: Geheimnisse werden aus einer Datei oder der Standardeingabe erstellt.
echo "my_secure_db_password" | docker secret create db_password_secret - echo "SG.your_api_key_here" | docker secret create sendgrid_api_key - ```
- Einen Dienst mit dem Geheimnis bereitstellen: Dienste referenzieren Geheimnisse nach Namen. Docker hängt das Geheimnis in den Container ein.
docker service create --name my-webapp
--secret db_password_secret
--secret sendgrid_api_key
my_app_image:latest
```
- Zugriff auf Geheimnisse im Container: Anwendungen lesen das Geheimnis aus dem eingehängten Dateipfad.
In Ihrem Python-Anwendungscode (oder ähnlich für andere Sprachen)
with open('/run/secrets/db_password_secret', 'r') as f: db_password = f.read().strip()
with open('/run/secrets/sendgrid_api_key', 'r') as f: sendgrid_key = f.read().strip() ```
Docker Compose und Secrets (für einzelne Hosts oder Swarm)
Docker Compose Version 3.1+ führte einen secrets-Abschnitt ein, der es Ihnen ermöglicht, Geheimnisse in Ihrer docker-compose.yml zu definieren und zu referenzieren. Wenn Sie im Swarm-Modus ausgeführt werden, nutzt Compose die nativen Geheimnisse von Docker Swarm. Wenn Sie auf einem einzelnen Host ohne Swarm-Modus ausgeführt werden, unterstützt Compose dennoch Geheimnisse, indem es Dateien vom Host sicher in den Container einhängt, allerdings ohne die Verschlüsselung im Ruhezustand, die Swarm bietet.
Verwendung von secrets in docker-compose.yml
Geheimnisse definieren: Sie können Geheimnisse entweder durch Referenzieren einer externen Datei oder durch Deklarieren als externes Geheimnis (vorab erstelltes Swarm-Geheimnis) definieren.
# docker-compose.yml version: '3.8' services: webapp: image: my_app_image:latest ports: - "80:8080" secrets: - db_password - sendgrid_api_key secrets: db_password: file: ./secrets/db_password.txt # Pfad zu einer Datei auf dem Host, die das Passwort enthält sendgrid_api_key: external: true # Bezieht sich auf ein bereits vorhandenes Docker Swarm-Geheimnis namens 'sendgrid_api_key'Lokale Geheimnisdateien erstellen (falls
fileverwendet wird):
mkdir secrets echo "my_local_db_password" > ./secrets/db_password.txt ```
Mit Compose bereitstellen:
docker compose up -dwird Ihre Dienste bereitstellen und die Geheimnisse unter/run/secrets/<secret_name>in den Containern verfügbar machen.# Innerhalb des Containers befindet sich der Inhalt von ./secrets/db_password.txt unter: # /run/secrets/db_password
Das richtige Werkzeug wählen: Konfiguration vs. Geheimnisse
Die Entscheidung, ob eine Umgebungsvariable für die Konfiguration oder eine dedizierte Geheimnisverwaltungslösung verwendet werden soll, läuft auf eine grundlegende Frage hinaus:
Sind die Daten sensibel?
- Wenn Ja (sensible Daten): Verwenden Sie Docker Secrets (mit Swarm) oder ein ähnliches Geheimnisverwaltungssystem (z. B. Kubernetes Secrets, HashiCorp Vault). Für Einzelhost-Compose-Setups verwenden Sie den Abschnitt
secrets, um Dateien sicher einzuhängen. - Wenn Nein (nicht sensible Konfiguration): Verwenden Sie Umgebungsvariablen (über
ENVim Dockerfile,-e-Flag,env_fileoderenvironmentin Compose).
| Funktion | Umgebungsvariablen (für Konfiguration) | Docker Secrets (für sensible Daten) |
|---|---|---|
| Zweck | Nicht sensible Anwendungskonfiguration | Sensible Daten wie Passwörter und API-Schlüssel |
| Sichtbarkeit | Sichtbar über docker inspect und oft Prozessinspektion |
Als Dateien eingehängt; nicht als normale Umgebungswerte angezeigt |
| Sicherheit | Nicht geeignet für sensible Daten | Stärkere Handhabung; Swarm-Geheimnisse sind im Ruhezustand verschlüsselt, während lokale Compose-Dateigeheimnisse vom Host-Dateischutz abhängen |
| Zugriff in der App | Lesen aus os.environ oder ähnlichem |
Lesen aus der Datei /run/secrets/<secret_name> |
| Verwaltet von | Docker-Laufzeit, Docker Compose | Docker Swarm, Docker Compose oder ein externer Geheimnisverwalter |
| Anwendungsfälle | Portnummern, Debug-Flags, nicht sensible URLs | Datenbankpasswörter, API-Token, private Schlüssel |
Best Practices für beide
Für die Konfiguration (Umgebungsvariablen):
- Geben Sie sinnvolle Standardwerte in Ihrem Dockerfile mit
ENVan. Dadurch sind Ihre Images sofort ausführbar und dokumentieren klar die erwarteten Variablen. - Externalisieren Sie die Konfiguration wo möglich. Verwenden Sie
.env-Dateien mitdocker composeoder externe Konfigurationsdienste für größere Bereitstellungen. - Dokumentieren Sie alle Konfigurationsoptionen und ihre erwarteten Werte, vielleicht in einer
README.mdoder in der Anwendungsdokumentation. - Vermeiden Sie das Hardcodieren von Werten, die sich zwischen verschiedenen Umgebungen (Entwicklung, Staging, Produktion) ändern könnten.
Für Geheimnisse (Docker Secrets und mehr):
- Übergeben Sie niemals Geheimnisse (z. B.
.env-Dateien mit Geheimnissen,db_password.txt) an Versionskontrollsysteme wie Git. - Rotieren Sie Geheimnisse regelmäßig. Dies minimiert das Zeitfenster der Offenlegung, falls ein Geheimnis kompromittiert wird.
- Gewähren Sie minimale Rechte. Geben Sie Diensten nur Zugriff auf die Geheimnisse, die sie unbedingt benötigen.
- Vermeiden Sie die Protokollierung von Geheimniswerten. Stellen Sie sicher, dass Ihre Anwendungs- und Infrastrukturprotokollierung keine Geheimnisinhalte ausgibt.
- Erwägen Sie für große, unternehmenskritische Bereitstellungen dedizierte Geheimnisverwaltungslösungen wie HashiCorp Vault, AWS Secrets Manager oder Azure Key Vault, die erweiterte Funktionen wie Prüfung, dynamische Geheimnisgenerierung und Integration mit Identity and Access Management (IAM) bieten.
Eine praktische Regel für die Entscheidung, was wohin gehört
Bevor Sie einen Wert zu environment hinzufügen, fragen Sie sich, was passieren würde, wenn ein Teammitglied ihn in ein Support-Ticket einfügen würde. Wenn die Antwort "nichts Ernstes" ist, handelt es sich wahrscheinlich um eine Konfiguration. Wenn die Antwort "wir müssten die Anmeldeinformationen rotieren" ist, handelt es sich um ein Geheimnis.
Gute Umgebungsvariablen:
APP_ENV=production
LOG_LEVEL=info
PUBLIC_BASE_URL=https://example.com
FEATURE_SIGNUP_ENABLED=false
REDIS_HOST=redis
Schlechte Umgebungsvariablen:
DATABASE_PASSWORD=...
STRIPE_SECRET_KEY=...
JWT_SIGNING_KEY=...
AWS_SECRET_ACCESS_KEY=...
PRIVATE_SSH_KEY=...
Es gibt Grauzonen. Ein Datenbank-Hostname ist normalerweise eine Konfiguration. Eine vollständige Datenbank-URL, die Benutzername und Passwort enthält, ist ein Geheimnis. Ein öffentlicher Analyse-Schlüssel kann für eine Browser-Anwendung sicher sein, während ein privater API-Token für denselben Anbieter dies nicht ist. Im Zweifelsfall behandeln Sie den Wert als sensibel, bis Sie das Gegenteil beweisen.
Compose .env-Dateien sind leicht misszuverstehen
Docker Compose verwendet .env auf zwei verschiedene Arten, die oft verwechselt werden.
Erstens liest Compose eine projektweite .env-Datei für die Variablensubstitution innerhalb von compose.yml:
services:
web:
image: "${APP_IMAGE}"
ports:
- "${HOST_PORT}:8080"
Zweitens übergibt env_file Variablen in den Container:
services:
web:
image: my-app
env_file:
- ./app.env
Diese Dateien mögen ähnlich aussehen, aber sie dienen unterschiedlichen Zwecken. Die erste hilft Compose, die Konfiguration zu rendern. Die zweite wird zur Laufzeitumgebung innerhalb des Containers. Gehen Sie nicht davon aus, dass ein Wert in der Projekt-.env automatisch im Container erscheint, es sei denn, Sie übergeben ihn explizit.
Für die lokale Entwicklung ist eine eingecheckte Beispieldatei hilfreich:
# .env.example
APP_ENV=development
LOG_LEVEL=debug
PUBLIC_BASE_URL=http://localhost:3000
Dann halten Sie die echte .env aus Git heraus:
.env
*.env.local
secrets/
Die Beispieldatei dokumentiert, was die App erwartet, ohne private Werte preiszugeben.
Dateibasierte Geheimnisse in einer Anwendung lesen
Viele Anwendungen erwarten Geheimnisse bereits in Umgebungsvariablen. Der Wechsel zu dateibasierten Geheimnissen ist einfacher, wenn Sie beide Muster eine Zeitlang unterstützen.
Zum Beispiel ein Node.js-Helfer:
import fs from "node:fs";
function readSecret(name) {
const filePath = process.env[`${name}_FILE`];
if (filePath) {
return fs.readFileSync(filePath, "utf8").trim();
}
return process.env[name];
}
const databasePassword = readSecret("DATABASE_PASSWORD");
Dann kann Ihre Compose-Datei auf eine eingehängte Geheimnisdatei verweisen:
services:
web:
image: my-app
environment:
DATABASE_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
secrets:
db_password:
file: ./secrets/db_password.txt
Dieses Muster funktioniert gut, da die Anwendung weiterhin in älteren Umgebungen laufen kann, während Sie die Produktion auf dateibasierte Geheimnisse umstellen. Viele offizielle Images unterstützen aus diesem Grund bereits Variablen, die auf _FILE enden.
Setzen Sie Geheimnisse auch nicht in Build-Argumente
Umgebungsvariablen sind nicht die einzige Falle. Auch Build-Argumente können undicht werden, wenn Sie sie zum Abrufen privater Pakete oder zum Klonen von Repositorys verwenden:
ARG NPM_TOKEN
RUN npm config set //registry.npmjs.org/:_authToken=$NPM_TOKEN
Selbst wenn der endgültige Container NPM_TOKEN nicht anzeigt, können Build-Verlauf und Zwischenebenen mehr preisgeben, als Sie erwarten. Verwenden Sie mit BuildKit geheime Mounts für Build-Zeit-Geheimnisse:
# syntax=docker/dockerfile:1.7
FROM node:22-slim
WORKDIR /app
COPY package.json package-lock.json ./
RUN --mount=type=secret,id=npm_token \
NPM_TOKEN="$(cat /run/secrets/npm_token)" npm ci
Bauen Sie es so:
docker build \
--secret id=npm_token,src=.npm-token \
-t my-app .
Das hält den Token aus dem Dockerfile heraus und vermeidet, ihn in eine normale Ebene einzubrennen. Sie müssen dennoch die lokale .npm-token-Datei und den CI-Geheimnisspeicher schützen.
Kubernetes, Cloud-Geheimnisverwalter und Docker
Docker Secrets sind in Swarm nützlich, und Compose-Geheimnisse sind nützlich für lokale oder Einzelhost-Setups. In Kubernetes verwenden Sie normalerweise Kubernetes Secrets, einen externen Secrets-Operator oder eine Integration mit einem Cloud-Geheimnisverwalter. Auf AWS verwenden Teams oft AWS Secrets Manager oder Systems Manager Parameter Store. Auf Azure ist Azure Key Vault üblich. Auf Google Cloud übernimmt Secret Manager dieselbe Rolle.
Das Prinzip ist plattformübergreifend dasselbe:
- Speichern Sie sensible Werte in einem System, das für Geheimnisse entwickelt wurde.
- Gewähren Sie der Laufzeitidentität nur Zugriff auf die Geheimnisse, die sie benötigt.
- Hängen Sie Geheimnisse zur Laufzeit ein oder injizieren Sie sie.
- Rotieren Sie Geheimnisse, ohne das Image neu zu erstellen.
- Halten Sie Geheimnisse aus der Quellcodeverwaltung, Image-Ebenen, Protokollen und Dashboards heraus.
Kubernetes Secrets sind standardmäßig kodiert, aber nicht automatisch in jeder Cluster-Konfiguration verschlüsselt. Viele verwaltete Cluster unterstützen die Verschlüsselung im Ruhezustand, aber überprüfen Sie die tatsächlichen Clustereinstellungen, anstatt es anzunehmen. Verwenden Sie für risikoreiche Anmeldeinformationen einen Cloud-Geheimnisverwalter oder ein dediziertes Tool mit Prüfprotokollen und Rotationsunterstützung.
Rotation ist Teil des Designs
Eine Geheimnisstrategie, die nicht rotiert werden kann, ist unvollständig. Stellen Sie diese Fragen vor der Produktion:
- Können wir das Datenbankpasswort ändern, ohne das Image neu zu erstellen?
- Können während eines Rollouts zwei gültige Anmeldeinformationen gleichzeitig existieren?
- Liest die Anwendung Geheimnisse erneut ein oder benötigt sie einen Neustart?
- Wo werden alte Anmeldeinformationen protokolliert, zwischengespeichert oder gespeichert?
- Wer wird benachrichtigt, wenn sich ein Geheimnis ändert?
Bei Datenbanken bedeutet Rotation oft das Erstellen einer zweiten Anmeldeinformation, das Bereitstellen der Anwendung mit der neuen Anmeldeinformation, das Überprüfen des Datenverkehrs und dann das Widerrufen der alten. Bei API-Schlüsseln hängt es vom Anbieter ab. Einige Dienste erlauben mehrere aktive Schlüssel; andere erzwingen eine Umstellung. Gestalten Sie Ihren Bereitstellungsprozess um die am wenigsten flexible Abhängigkeit herum.
Versehentliche Offenlegung bereinigen
Wenn ein Geheimnis bereits in Git eingecheckt oder in ein Image eingebrannt wurde, reicht es nicht aus, die Zeile zu löschen. Behandeln Sie es als offengelegt.
Die übliche Reaktion ist:
- Widerrufen oder rotieren Sie die Anmeldeinformation.
- Entfernen Sie sie aus dem aktuellen Code oder Image.
- Überprüfen Sie CI-Protokolle, Image-Registries, Issue-Tracker und Chat-Nachrichten auf Kopien.
- Schreiben Sie die Git-Historie nur um, wenn Ihr Unternehmen auf die Koordination vorbereitet ist; Rotation ist dennoch erforderlich.
- Fügen Sie Scans oder Pre-Commit-Checks hinzu, um wiederholte Fehler zu reduzieren.
Tools können helfen, aber sie ersetzen keine Gewohnheiten. Benennen Sie Geheimnisdateien klar, ignorieren Sie sie in Git und vermeiden Sie es, Konfigurationsobjekte beim Start vollständig auszugeben.
Das funktionierende Muster
Verwenden Sie Umgebungsvariablen für Werte, die beschreiben, wie die App in dieser Umgebung laufen soll: Ports, Log-Level, Feature-Flags, Diensthostnamen und nicht sensible URLs. Verwenden Sie Geheimnisse für Werte, die Identität nachweisen oder Zugriff gewähren: Passwörter, Token, Signierschlüssel, private Schlüssel und Anbieter-Anmeldeinformationen.
Das saubere Docker-Image ist in allen Umgebungen gleich. Entwicklung, Staging und Produktion ändern das Verhalten zur Laufzeit. Die Konfiguration kann als Umgebungsvariablen reisen. Geheimnisse sollten aus einem Geheimnisspeicher oder einer eingehängten Geheimnisdatei mit eingeschränktem Zugriff stammen. Diese Trennung hält Bereitstellungen flexibel, ohne jede Container-Inspektion, Protokollzeile oder Image-Ebene in ein Anmeldeinformationsleck zu verwandeln.