Back to Blog
docker devops best-practices

Docker Compose Projekt-Struktur

Nach hunderten Docker-Projekten hat sich eine Struktur herauskristallisiert, die einfach funktioniert.

Die Struktur

projekt/
├── docker-compose.yml
├── docker-compose.override.yml  (optional, für Dev)
├── .env
├── .env.example
├── config/
│   ├── nginx/
│   │   └── nginx.conf
│   └── app/
│       └── config.json
├── data/
│   └── .gitkeep
├── scripts/
│   ├── backup.sh
│   └── restore.sh
└── README.md

docker-compose.yml

Die Basis. Funktioniert in jeder Umgebung:

services:
  app:
    image: myapp:${TAG:-latest}
    restart: unless-stopped
    env_file:
      - .env
    volumes:
      - ./config/app:/config:ro
      - ./data:/data
    networks:
      - internal

  db:
    image: postgres:16-alpine
    restart: unless-stopped
    env_file:
      - .env
    volumes:
      - db_data:/var/lib/postgresql/data
    networks:
      - internal
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER}"]
      interval: 10s
      timeout: 5s
      retries: 5

networks:
  internal:

volumes:
  db_data:

docker-compose.override.yml

Automatisch geladen in Dev. Ports exponieren, Debug-Mode, etc:

services:
  app:
    ports:
      - "3000:3000"
    environment:
      - DEBUG=true
    volumes:
      - ./src:/app/src  # Hot-Reload

  db:
    ports:
      - "5432:5432"

.env und .env.example

.env.example - Template mit Kommentaren (committed):

# Database
POSTGRES_USER=app
POSTGRES_PASSWORD=changeme
POSTGRES_DB=myapp

# App
APP_SECRET=changeme
TAG=latest

.env - Echte Werte (NICHT committed!):

POSTGRES_USER=app
POSTGRES_PASSWORD=super-sicheres-passwort-123
POSTGRES_DB=myapp
APP_SECRET=noch-sichereres-geheimnis
TAG=1.2.3

.gitignore:

.env
data/*
!data/.gitkeep

Config-Verzeichnis

Für Konfigurationsdateien die gemountet werden:

config/
├── nginx/
│   ├── nginx.conf
│   └── sites/
│       └── app.conf
└── app/
    └── config.json

Read-only mounten:

volumes:
  - ./config/nginx:/etc/nginx/conf.d:ro

Data-Verzeichnis

Für persistente Daten die NICHT in ein Volume sollen:

volumes:
  - ./data/uploads:/app/uploads

Wann ./data vs Docker Volume?

  • Docker Volume: Datenbank, Cache - schneller, managed
  • Bind Mount ./data: Uploads, Logs - einfacher Zugriff, Backup

Scripts

Wiederkehrende Aufgaben:

#!/bin/bash
# scripts/backup.sh
docker compose exec -T db pg_dump -U $POSTGRES_USER $POSTGRES_DB > backup.sql
#!/bin/bash
# scripts/restore.sh
docker compose exec -T db psql -U $POSTGRES_USER $POSTGRES_DB < backup.sql

Mehrere Umgebungen

Für Staging/Prod separate Compose-Files:

├── docker-compose.yml         # Basis
├── docker-compose.override.yml # Dev (auto-loaded)
├── docker-compose.staging.yml
└── docker-compose.prod.yml
# Staging
docker compose -f docker-compose.yml -f docker-compose.staging.yml up -d

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

Meine Regeln

  1. Ein Projekt, ein Verzeichnis
  2. .env nie committen
  3. Healthchecks für alle Services
  4. restart: unless-stopped für Produktion
  5. Versions-Tags statt in Prod

Fazit

Konsistente Struktur = weniger Kopfschmerzen. Einmal richtig aufsetzen, immer wieder verwenden.

Made with by Daniel Hiller

|