DevToolBoxKOSTENLOS
Blog

Docker Compose Secrets & Umgebungsvariablen: Der richtige Weg

10 Min. Lesezeitvon DevToolBox

Umgebungsvariablen sind der primaere Mechanismus zur Konfiguration von Containern in Docker Compose. Es gibt jedoch 4 verschiedene Wege, sie zu uebergeben, jeder mit unterschiedlichem Verhalten, Sicherheitsimplikationen und Fallstricken. Dieser Leitfaden behandelt alle Ansaetze.

4 Wege zur Uebergabe von Umgebungsvariablen in Docker Compose

Docker Compose unterstuetzt vier verschiedene Methoden zur Injektion von Umgebungsvariablen in Container.

1. Inline mit der environment-Direktive

Schluessel-Wert-Paare direkt in docker-compose.yml definieren. Am besten fuer nicht-sensible, service-spezifische Konfiguration.

services:
  api:
    image: node:20-alpine
    environment:
      NODE_ENV: production
      DB_HOST: postgres
      DB_PORT: "5432"
      LOG_LEVEL: info

  # List syntax (also valid)
  worker:
    image: node:20-alpine
    environment:
      - NODE_ENV=production
      - QUEUE_NAME=tasks
      - CONCURRENCY=4

2. Externe Datei mit env_file

Variablen aus externen Dateien laden. Am besten fuer die Verwaltung zusammengehoeriger Variablen.

services:
  api:
    image: node:20-alpine
    env_file:
      - .env              # Shared variables
      - .env.api          # Service-specific variables
      - .env.local        # Local overrides (gitignored)

  db:
    image: postgres:16
    env_file:
      - .env
      - .env.db

3. Shell-Umgebungsvariablen-Durchreichung

Host-Shell-Variablen an den Container weiterleiten. Am besten fuer CI/CD-Pipelines.

# In your shell:
export API_KEY=sk-abc123
export DEBUG=true

# docker-compose.yml
services:
  api:
    image: node:20-alpine
    environment:
      - API_KEY          # Passes host $API_KEY into container
      - DEBUG            # Passes host $DEBUG into container
      - CI               # Passes host $CI (empty string if unset)

4. Variablensubstitution in docker-compose.yml

Host-Umgebungsvariablen direkt in Compose-Dateiwerten interpolieren.

# .env file
POSTGRES_VERSION=16
APP_PORT=3000
REPLICAS=3

# docker-compose.yml
services:
  db:
    image: postgres:${POSTGRES_VERSION}    # Resolved from .env or shell
  app:
    ports:
      - "${APP_PORT}:3000"                 # Resolved from .env or shell
    deploy:
      replicas: ${REPLICAS}               # Resolved from .env or shell

Vergleichstabelle

MethodeAm besten fuerIn VCS?Secret-sicher?
environmentNon-sensitive defaultsYesNo
env_fileGrouped config, secretsNo (.gitignore)Partial
Shell passthroughCI/CD, dynamic valuesN/APartial
${} substitutionCompose file templatingYes (file), No (.env)No

.env-Datei: Syntax, Platzierung und Interpolation

Docker Compose liest automatisch eine .env-Datei im selben Verzeichnis wie docker-compose.yml.

Syntaxregeln

# Comments start with #
# Blank lines are ignored

# Simple key=value (no spaces around =)
DB_HOST=localhost
DB_PORT=5432

# Values with spaces need quotes
APP_NAME="My Docker App"
GREETING='Hello World'

# Multi-line values (double quotes only)
PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA...
-----END RSA PRIVATE KEY-----"

# Variable expansion (Compose v2.20+)
BASE_URL=https://api.example.com
HEALTH_URL=${BASE_URL}/health

# Export prefix is ignored (compatible with shell source)
export SECRET_KEY=mysecretkey

# Empty values
EMPTY_VAR=
ALSO_EMPTY=''

Dateiplatzierung und Prioritaet

Compose sucht .env im Projektverzeichnis. Mit --env-file ueberschreibbar:

# Default: reads .env from project directory
docker compose up

# Override with --env-file
docker compose --env-file .env.staging up

# Multiple --env-file flags (Compose v2.24+)
docker compose --env-file .env --env-file .env.local up

# Project directory structure:
my-project/
  docker-compose.yml    # Compose file
  .env                  # Auto-loaded by Compose
  .env.staging          # Loaded with --env-file
  .env.production       # Loaded with --env-file

Interpolation in docker-compose.yml

Variablen aus .env stehen fuer die Substitution in docker-compose.yml zur Verfuegung, werden aber NICHT automatisch in Container injiziert:

# .env
TAG=3.2.1
EXTERNAL_PORT=8080

# docker-compose.yml
services:
  web:
    image: myapp:${TAG}              # Uses TAG from .env -> myapp:3.2.1
    ports:
      - "${EXTERNAL_PORT}:80"        # Uses EXTERNAL_PORT from .env -> 8080:80
    environment:
      - TAG                           # WRONG! This passes host $TAG, not .env TAG
      - TAG=${TAG}                   # RIGHT! Explicitly interpolate .env value

# Key insight:
# .env -> docker-compose.yml interpolation   (automatic)
# .env -> container environment              (NOT automatic, use env_file)

env_file vs environment: Unterschiede und Fallstricke

Diese beiden Direktiven sehen aehnlich aus, verhalten sich aber unterschiedlich.

DirektiveVerhalten
environment:Inlined in docker-compose.yml. Visible in version control. Supports variable substitution. Overrides env_file.
env_file:Loaded from external file(s). Can be gitignored. No variable substitution inside the file. Lower priority than environment.
.env (auto)Only for Compose file interpolation (${} syntax). NOT injected into containers. Lowest priority.

Kritische Fallstricke

  • 1. Prioritaet: environment-Werte ueberschreiben env_file-Werte fuer denselben Schluessel.
  • 2. Die .env-Datei (automatisch geladen) dient nur der Compose-Datei-Interpolation. Sie injiziert KEINE Variablen in Container.
  • 3. env_file-Pfade sind relativ zur docker-compose.yml-Datei, nicht zum aktuellen Arbeitsverzeichnis.
  • 4. Wenn eine env_file fehlt, schlaegt Compose fehl. Verwenden Sie required: false (Compose v2.24+).
# Precedence demonstration:

# .env.shared
DB_HOST=shared-db
DB_PORT=5432

# docker-compose.yml
services:
  api:
    env_file:
      - .env.shared              # DB_HOST=shared-db, DB_PORT=5432
    environment:
      DB_HOST: override-db       # Overrides env_file! DB_HOST=override-db
      # DB_PORT not listed here  # Keeps env_file value: DB_PORT=5432

# Result inside container:
# DB_HOST=override-db    (from environment, overrides env_file)
# DB_PORT=5432           (from env_file)

# Optional env_file (Compose v2.24+):
services:
  api:
    env_file:
      - path: .env.local
        required: false          # Won't fail if file doesn't exist

Docker Secrets: Dateibasierte Secrets fuer die Produktion

Docker Secrets bieten eine sicherere Alternative zu Umgebungsvariablen fuer sensible Daten.

Schritt 1: Secret-Dateien erstellen

# Create secret files (don't commit these!)
echo "SuperSecretPassword123" > db_password.txt
echo "sk-prod-abc123xyz789" > api_key.txt

# Add to .gitignore
echo "*.txt" >> .gitignore
echo "secrets/" >> .gitignore

# Alternative: use a secrets directory
mkdir secrets
echo "SuperSecretPassword123" > secrets/db_password
echo "sk-prod-abc123xyz789" > secrets/api_key

Schritt 2: Secrets in docker-compose.yml definieren

# docker-compose.yml
services:
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password  # Note: _FILE suffix
    secrets:
      - db_password                    # Grant access to this secret

  api:
    image: myapp:latest
    secrets:
      - db_password
      - api_key
    # Secrets available at:
    # /run/secrets/db_password
    # /run/secrets/api_key

# Top-level secrets definition
secrets:
  db_password:
    file: ./secrets/db_password        # From local file
  api_key:
    file: ./secrets/api_key            # From local file

  # Alternative: use environment variable as source
  # jwt_secret:
  #   environment: JWT_SECRET_VALUE    # From host env var (Compose v2.23+)

Schritt 3: Secrets in der Anwendung lesen

# Inside the container, secrets are plain text files:
$ docker compose exec api cat /run/secrets/db_password
SuperSecretPassword123

$ docker compose exec api cat /run/secrets/api_key
sk-prod-abc123xyz789

$ docker compose exec api ls -la /run/secrets/
total 8
-r--r--r-- 1 root root 24 Jan  1 00:00 db_password
-r--r--r-- 1 root root 22 Jan  1 00:00 api_key

Schritt 4: Secrets im Code verarbeiten

// Node.js - Read secret from file
const fs = require('fs');

function getSecret(name) {
  const secretPath = `/run/secrets/${name}`;
  try {
    return fs.readFileSync(secretPath, 'utf8').trim();
  } catch (err) {
    // Fallback to environment variable (for development)
    return process.env[name.toUpperCase()];
  }
}

const dbPassword = getSecret('db_password');
const apiKey = getSecret('api_key');
# Python - Read secret from file
import os

def get_secret(name: str) -> str:
    secret_path = f"/run/secrets/{name}"
    try:
        with open(secret_path) as f:
            return f.read().strip()
    except FileNotFoundError:
        # Fallback to environment variable
        return os.environ.get(name.upper(), "")

db_password = get_secret("db_password")
api_key = get_secret("api_key")

Warum Secrets statt Umgebungsvariablen?

  • Umgebungsvariablen koennen ueber docker inspect, /proc/environ und Fehlerprotokolle lecken
  • Secrets sind dateibasiert und nur in /run/secrets/ zugaenglich
  • Secrets unterstuetzen granulare Zugriffskontrolle pro Service
  • Viele Datenbanken unterstuetzen nativ den _FILE-Suffix fuer Secret-Dateien

Variablensubstitution: ${VAR:-default}-Syntax

Docker Compose unterstuetzt Shell-aehnliche Variablensubstitution mit Standardwerten.

SyntaxErgebnis
${VAR}Value of VAR. Error if unset.
${VAR:-default}Value of VAR if set, otherwise "default".
${VAR-default}Value of VAR if set (even if empty), otherwise "default".
${VAR:?error msg}Value of VAR if set, otherwise exit with "error msg".
${VAR?error msg}Value of VAR if set (even if empty), otherwise exit with "error msg".
${VAR:+replacement}"replacement" if VAR is set and non-empty, otherwise empty.
${VAR+replacement}"replacement" if VAR is set (even if empty), otherwise empty.

Praktisches Beispiel

# docker-compose.yml with variable substitution
services:
  web:
    image: nginx:${NGINX_VERSION:-1.25-alpine}     # Default: 1.25-alpine
    ports:
      - "${WEB_PORT:-80}:80"                        # Default: 80
      - "${SSL_PORT:-443}:443"                      # Default: 443
    volumes:
      - ${CONFIG_PATH:-./nginx.conf}:/etc/nginx/nginx.conf:ro

  api:
    image: ${REGISTRY:-docker.io}/myapp:${TAG:?TAG is required}
    #                                     ^ Fails if TAG is not set
    environment:
      NODE_ENV: ${NODE_ENV:-development}
      LOG_LEVEL: ${LOG_LEVEL:-info}
      DATABASE_URL: postgres://${DB_USER:-postgres}:${DB_PASS:?DB_PASS required}@db:5432/${DB_NAME:-myapp}

  db:
    image: postgres:${PG_VERSION:-16}
    volumes:
      - ${DATA_DIR:-./data}/postgres:/var/lib/postgresql/data

Multi-Umgebungs-Setup: Dev/Staging/Production-Muster

Reale Projekte benoetigen unterschiedliche Konfigurationen. Hier sind bewaehrte Muster.

Muster 1: Override-Dateien (empfohlen)

Verwenden Sie eine Basis-docker-compose.yml mit umgebungsspezifischen Override-Dateien.

# File structure:
project/
  docker-compose.yml              # Base configuration
  docker-compose.override.yml     # Dev overrides (auto-loaded!)
  docker-compose.staging.yml      # Staging overrides
  docker-compose.prod.yml         # Production overrides
# docker-compose.yml (base)
services:
  api:
    image: myapp:${TAG:-latest}
    restart: unless-stopped
    depends_on:
      - db
      - redis

  db:
    image: postgres:16
    volumes:
      - pg-data:/var/lib/postgresql/data

  redis:
    image: redis:7-alpine

volumes:
  pg-data:
# docker-compose.override.yml (development - auto-loaded)
services:
  api:
    build: .
    volumes:
      - .:/app                          # Hot reload
      - /app/node_modules
    ports:
      - "3000:3000"
      - "9229:9229"                     # Debugger
    environment:
      NODE_ENV: development
      LOG_LEVEL: debug
      DB_HOST: db

  db:
    ports:
      - "5432:5432"                     # Expose DB for local tools
    environment:
      POSTGRES_PASSWORD: devpassword    # OK for dev only
# docker-compose.prod.yml (production)
services:
  api:
    # No build, no volumes, no debug port
    ports:
      - "3000:3000"
    environment:
      NODE_ENV: production
      LOG_LEVEL: warn
    env_file:
      - .env.production
    secrets:
      - db_password
      - api_key
    deploy:
      replicas: 3
      resources:
        limits:
          cpus: '1.0'
          memory: 512M

  db:
    # No exposed ports
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      - db_password
    deploy:
      resources:
        limits:
          cpus: '2.0'
          memory: 1G

secrets:
  db_password:
    file: ./secrets/db_password
  api_key:
    file: ./secrets/api_key
# Usage:

# Development (auto-loads docker-compose.override.yml)
docker compose up

# Staging
docker compose -f docker-compose.yml -f docker-compose.staging.yml up -d

# Production
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

# Validate merged config before deploying
docker compose -f docker-compose.yml -f docker-compose.prod.yml config

Muster 2: Umgebungsspezifische .env-Dateien

Verwenden Sie --env-file zum Laden verschiedener Variablensets pro Umgebung.

# .env.development
TAG=latest
APP_PORT=3000
DB_PASSWORD=devpassword
LOG_LEVEL=debug
REPLICAS=1

# .env.staging
TAG=rc-1.2.3
APP_PORT=3000
DB_PASSWORD=staging-secret-pw
LOG_LEVEL=info
REPLICAS=2

# .env.production
TAG=1.2.3
APP_PORT=3000
DB_PASSWORD=prod-ultra-secret-pw
LOG_LEVEL=warn
REPLICAS=3

# Usage:
docker compose --env-file .env.development up       # Dev
docker compose --env-file .env.staging up -d         # Staging
docker compose --env-file .env.production up -d      # Production

Muster 3: Profiles fuer optionale Services

Verwenden Sie Compose-Profiles zum Ein- oder Ausschliessen von Services pro Umgebung.

# docker-compose.yml
services:
  api:
    image: myapp:${TAG:-latest}
    ports:
      - "3000:3000"

  db:
    image: postgres:16
    volumes:
      - pg-data:/var/lib/postgresql/data

  # Dev-only services
  adminer:
    image: adminer
    ports:
      - "8080:8080"
    profiles:
      - dev                           # Only starts with --profile dev

  mailhog:
    image: mailhog/mailhog
    ports:
      - "1025:1025"
      - "8025:8025"
    profiles:
      - dev                           # Only starts with --profile dev

  # Monitoring (staging + production)
  prometheus:
    image: prom/prometheus
    profiles:
      - monitoring                    # Only starts with --profile monitoring

volumes:
  pg-data:

# Usage:
docker compose --profile dev up                     # Includes adminer + mailhog
docker compose --profile monitoring up -d           # Includes prometheus
docker compose --profile dev --profile monitoring up  # Both profiles

Sicherheitscheckliste: Was NIEMALS in Umgebungsvariablen gehoert

Umgebungsvariablen sind praktisch, aber fuer sensible Daten inherent unsicher.

NIEMALSStattdessen
Passwoerter in docker-compose.yml hartcodieren.env-Dateien (gitignored) oder Docker Secrets verwenden
.env-Dateien in Versionskontrolle committen.env zu .gitignore hinzufuegen, .env.example committen
Private Schluessel ueber Umgebungsvariablen uebergebenAls Dateien ueber Volumes oder Docker Secrets mounten
Umgebungsvariablen im Anwendungscode protokollierenSensible Werte in Logs maskieren
.env-Dateien ueber Slack oder E-Mail teilenEinen Secrets-Manager verwenden (Vault, AWS Secrets Manager)
Gleiche Secrets in dev/staging/production verwendenEinzigartige Secrets pro Umgebung generieren
# .gitignore — essential entries for Docker projects
.env
.env.*
!.env.example
secrets/
*.pem
*.key
*.crt
# .env.example — commit this as a template
# Database
DB_HOST=localhost
DB_PORT=5432
DB_USER=myapp
DB_PASSWORD=CHANGE_ME

# API Keys
API_KEY=CHANGE_ME
JWT_SECRET=CHANGE_ME

# Application
NODE_ENV=development
LOG_LEVEL=debug
APP_PORT=3000

Debugging: Warum laedt meine Umgebungsvariable nicht?

Folgen Sie diesem systematischen Ablauf, wenn eine Umgebungsvariable Ihren Container nicht erreicht.

Schritt 1: Ist die Variable definiert?

Pruefen Sie, ob die Variable in .env, env_file oder environment-Direktive existiert.

Schritt 2: Erreicht sie den Container?

Fuehren Sie docker compose exec <service> env aus, um alle Variablen zu sehen.

Schritt 3: Ist die .env-Datei am richtigen Ort?

Die .env-Datei muss im selben Verzeichnis wie docker-compose.yml sein.

Schritt 4: Gibt es einen Namenskonflikt?

Pruefen Sie Tippfehler, Gross-/Kleinschreibung und Prioritaet.

Schritt 5: Ist die Compose-Datei gueltig?

Fuehren Sie docker compose config aus, um die aufgeloeste Konfiguration zu sehen.

Schritt 6: Wird die Variable ueberschrieben?

Shell-Umgebungsvariablen ueberschreiben .env-Werte. Pruefen Sie mit echo $VAR_NAME.

Nuetzliche Debug-Befehle

# 1. See the fully resolved Compose config (all variables substituted)
docker compose config

# 2. Check environment variables inside a running container
docker compose exec api env

# 3. Check a specific variable
docker compose exec api sh -c 'echo $DATABASE_URL'

# 4. Inspect container config (shows env vars, mounts, etc.)
docker inspect <container_id> --format '{{json .Config.Env}}' | jq .

# 5. Check if .env file is being read
docker compose config --format json | jq '.services.api.environment'

# 6. Verify env_file exists and is readable
docker compose config 2>&1 | grep -i "env_file"

# 7. Run a one-off container to test environment
docker compose run --rm api env | sort

# 8. Check for shell variable conflicts
env | grep -i "DB_"

# 9. Validate Compose file syntax
docker compose config --quiet && echo "Valid" || echo "Invalid"

# 10. Show variable substitution warnings
docker compose --verbose up 2>&1 | grep -i "variable"

Haeufig gestellte Fragen

Was ist der Unterschied zwischen .env und env_file in Docker Compose?

Die .env-Datei wird automatisch fuer die Variablensubstitution in docker-compose.yml gelesen. Die env_file-Direktive laedt Variablen direkt in die Container-Umgebung.

Wie uebergebe ich Host-Umgebungsvariablen an einen Container?

Listen Sie den Variablennamen ohne Wert in der environment-Direktive auf oder verwenden Sie ${MY_VAR} in docker-compose.yml.

Sind Docker-Umgebungsvariablen sicher?

Nein. Sie sind ueber docker inspect sichtbar und erscheinen in /proc/environ. Verwenden Sie Docker Secrets fuer sensible Daten.

Welche Prioritaetsreihenfolge haben Umgebungsvariablen?

Von hoch nach niedrig: 1) docker compose run -e, 2) Host-Shell-Variablen, 3) environment-Direktive, 4) env_file-Direktive, 5) Dockerfile ENV.

Wie verwende ich verschiedene Variablen fuer Dev und Production?

Override-Dateien verwenden: Basis docker-compose.yml + docker-compose.override.yml fuer Dev + docker-compose.prod.yml fuer Production.

Was ist der Unterschied zwischen Docker Secrets und Umgebungsvariablen?

Secrets werden als Dateien in /run/secrets/ gemountet statt als Umgebungsvariablen gesetzt. Sie sind sicherer und unterstuetzen den _FILE-Suffix.

𝕏 Twitterin LinkedIn
War das hilfreich?

Bleiben Sie informiert

Wöchentliche Dev-Tipps und neue Tools.

Kein Spam. Jederzeit abbestellbar.

Verwandte Tools ausprobieren

🐳Docker Compose GeneratorYMLYAML Validator & Formatter.gi.gitignore Generator

Verwandte Artikel

Docker Compose Spickzettel: Services, Volumes und Netzwerke

Docker Compose Referenz: Service-Definitionen, Volumes, Netzwerke, Umgebungsvariablen und Stack-Beispiele.

.env Datei Guide: Best Practices fĂŒr Umgebungsvariablen

Meistern Sie .env Dateien und Umgebungsvariablen.