DevToolBoxFREE
BlogAdvertise

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.

War das hilfreich?

Stay Updated

Get weekly dev tips and new tool announcements.

No spam. Unsubscribe anytime.

Partner Picks

Sponsor this article

Place your product next to this developer topic with tracked clicks.

Ask about article sponsorship

This site uses cookies for analytics and to display ads. By continuing to browse, you agree. Privacy Policy