Docker Compose für Entwicklung: Lokale Infrastruktur
Der Unterschied zwischen einer angenehmen Onboarding-Erfahrung („klonen und ausführen pnpm dev:infra“) und einer schmerzhaften („zuerst PostgreSQL einrichten, dann Redis konfigurieren, dann ...“) hängt davon ab, wie gut Ihr Docker Compose-Setup Ihre Infrastrukturanforderungen erfasst. Mit einem gut gestalteten docker-compose.dev.yml kann jeder Entwickler auf jedem Computer innerhalb von Minuten genau dieselbe Infrastruktur ausführen.
In diesem Leitfaden werden die Muster für einen lokalen Entwicklungsstack in Produktionsqualität behandelt: Dienstkonfiguration, Zustandsprüfungen, Netzwerk, Volume-Management und die Integration in die Startsequenz Ihrer Anwendung.
Wichtige Erkenntnisse
– Verwenden Sie lokal einen nicht standardmäßigen Port für PostgreSQL (5433), um Konflikte mit Systeminstallationen zu vermeiden
- Integritätsprüfungen für Dienstabhängigkeiten verhindern Startfehler „Verbindung abgelehnt“. – Benannte Volumes behalten Datenbankdaten zwischen Container-Neustarts bei – Bind-Mounts funktionieren unter Windows nicht zuverlässig – Verwenden Sie
env_file, um Umgebungsvariablen aus Ihrer.env.local-Datei in Container zu laden- Trennen Sie
docker-compose.dev.ymlvondocker-compose.prod.yml– sie dienen unterschiedlichen Zwecken – Dasdepends_on.condition: service_healthy-Muster wartet auf die tatsächliche Bereitschaft, nicht nur auf den Start des Containers- Verwenden Sie
profiles, um optionale Dienste (E-Mail, Überwachung) zu aktivieren- Führen Sie
docker compose(v2) und nichtdocker-compose(v1) aus – die Plugin-Syntax ist aktuell
Der komplette Entwicklungs-Stack
# infrastructure/docker-compose.dev.yml
name: ecosire-dev
services:
# ─── PostgreSQL ─────────────────────────────────────────────────
postgres:
image: postgres:17-alpine
container_name: ecosire-postgres
environment:
POSTGRES_DB: ecosire_dev
POSTGRES_USER: ecosire
POSTGRES_PASSWORD: dev_password_change_in_prod
ports:
- "5433:5432" # 5433 externally — avoids conflicts with system PostgreSQL
volumes:
- postgres_data:/var/lib/postgresql/data
- ./init-scripts:/docker-entrypoint-initdb.d # Run SQL on first start
command: >
postgres
-c shared_buffers=256MB
-c effective_cache_size=1GB
-c work_mem=16MB
-c maintenance_work_mem=128MB
-c checkpoint_completion_target=0.9
-c wal_buffers=16MB
-c max_connections=100
-c log_min_duration_statement=100
-c log_statement=ddl
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ecosire -d ecosire_dev"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
restart: unless-stopped
# ─── Redis ──────────────────────────────────────────────────────
redis:
image: redis:7-alpine
container_name: ecosire-redis
ports:
- "6379:6379"
volumes:
- redis_data:/data
command: >
redis-server
--maxmemory 512mb
--maxmemory-policy allkeys-lru
--appendonly yes
--appendfsync everysec
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
# ─── Authentik (Identity Provider) ──────────────────────────────
authentik-server:
image: ghcr.io/goauthentik/server:2024.12
container_name: ecosire-authentik
command: server
environment:
AUTHENTIK_REDIS__HOST: redis
AUTHENTIK_POSTGRESQL__HOST: postgres
AUTHENTIK_POSTGRESQL__USER: ecosire
AUTHENTIK_POSTGRESQL__PASSWORD: dev_password_change_in_prod
AUTHENTIK_POSTGRESQL__NAME: authentik
AUTHENTIK_SECRET_KEY: dev-secret-key-change-in-production-32chars
AUTHENTIK_ERROR_REPORTING__ENABLED: "false"
AUTHENTIK_DISABLE_STARTUP_ANALYTICS: "true"
volumes:
- authentik_media:/media
- authentik_certs:/certs
ports:
- "9000:9000" # HTTP
- "9443:9443" # HTTPS
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
healthcheck:
test: ["CMD-SHELL", "ak healthcheck"]
interval: 30s
timeout: 10s
retries: 5
start_period: 60s # Authentik takes time to initialize
restart: unless-stopped
authentik-worker:
image: ghcr.io/goauthentik/server:2024.12
container_name: ecosire-authentik-worker
command: worker
environment:
AUTHENTIK_REDIS__HOST: redis
AUTHENTIK_POSTGRESQL__HOST: postgres
AUTHENTIK_POSTGRESQL__USER: ecosire
AUTHENTIK_POSTGRESQL__PASSWORD: dev_password_change_in_prod
AUTHENTIK_POSTGRESQL__NAME: authentik
AUTHENTIK_SECRET_KEY: dev-secret-key-change-in-production-32chars
volumes:
- authentik_media:/media
- authentik_certs:/certs
- /var/run/docker.sock:/var/run/docker.sock # For Authentik's proxy
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
restart: unless-stopped
# ─── Mailpit (Email Testing) ─────────────────────────────────────
mailpit:
image: axllent/mailpit:latest
container_name: ecosire-mailpit
ports:
- "1025:1025" # SMTP
- "8025:8025" # Web UI
environment:
MP_MAX_MESSAGES: 200
MP_SMTP_AUTH_ACCEPT_ANY: true
MP_SMTP_AUTH_ALLOW_INSECURE: true
restart: unless-stopped
profiles:
- email # Optional — use `docker compose --profile email up`
# ─── pgAdmin (Database GUI) ─────────────────────────────────────
pgadmin:
image: dpage/pgadmin4:latest
container_name: ecosire-pgadmin
environment:
PGADMIN_DEFAULT_EMAIL: [email protected]
PGADMIN_DEFAULT_PASSWORD: admin
PGADMIN_CONFIG_SERVER_MODE: "False"
PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED: "False"
ports:
- "5050:80"
volumes:
- pgadmin_data:/var/lib/pgadmin
depends_on:
postgres:
condition: service_healthy
restart: unless-stopped
profiles:
- tools # Optional
networks:
default:
name: ecosire-dev-network
volumes:
postgres_data:
name: ecosire-postgres-data
redis_data:
name: ecosire-redis-data
authentik_media:
name: ecosire-authentik-media
authentik_certs:
name: ecosire-authentik-certs
pgadmin_data:
name: ecosire-pgadmin-data
Package.json-Skripte
Verknüpfen Sie die Docker Compose-Befehle mit Ihren Monorepo-Skripten:
{
"scripts": {
"dev:infra": "docker compose -f infrastructure/docker-compose.dev.yml up -d",
"dev:infra:down": "docker compose -f infrastructure/docker-compose.dev.yml down",
"dev:infra:logs": "docker compose -f infrastructure/docker-compose.dev.yml logs -f",
"dev:infra:reset": "docker compose -f infrastructure/docker-compose.dev.yml down -v && pnpm dev:infra",
"dev:infra:email": "docker compose -f infrastructure/docker-compose.dev.yml --profile email up -d",
"dev:infra:tools": "docker compose -f infrastructure/docker-compose.dev.yml --profile tools up -d"
}
}
Das Flag --profile lässt optionale Dienste (E-Mail-Testen mit Mailpit, Datenbank-GUI mit pgAdmin) inaktiv bleiben, bis sie explizit angefordert werden.
Datenbankinitialisierungsskripte
Platzieren Sie SQL-Dateien in infrastructure/init-scripts/ – sie werden beim ersten Containerstart ausgeführt:
-- infrastructure/init-scripts/01-create-databases.sql
-- Create all databases Authentik needs separately from the app DB
CREATE DATABASE authentik;
GRANT ALL PRIVILEGES ON DATABASE authentik TO ecosire;
-- Create test database for CI
CREATE DATABASE ecosire_test;
GRANT ALL PRIVILEGES ON DATABASE ecosire_test TO ecosire;
-- infrastructure/init-scripts/02-extensions.sql
-- Enable PostgreSQL extensions
\c ecosire_dev;
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pg_trgm"; -- Trigram search
CREATE EXTENSION IF NOT EXISTS "btree_gin"; -- Composite GIN indexes
\c ecosire_test;
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
Initialisierungsskripte werden in alphabetischer Reihenfolge ausgeführt. Der psql-Metabefehl \c database_name wechselt die aktive Datenbank.
Integration von Umgebungsvariablen
Ihre Anwendung liest von .env.local im Monorepo-Stammverzeichnis. Die Docker-Dienste müssen wissen, wie sie mithilfe von Dienstnamen (nicht localhost) eine Verbindung zueinander herstellen können:
# .env.local (monorepo root)
# PostgreSQL — use 5433 externally (host) or 5432 internally (container network)
DATABASE_URL=postgresql://ecosire:dev_password_change_in_prod@localhost:5433/ecosire_dev
# Redis
REDIS_URL=redis://localhost:6379
# Authentik — use 9000 for external calls from your dev machine
AUTHENTIK_URL=http://localhost:9000
# Use service name for server-to-server calls within Docker network
AUTHENTIK_INTERNAL_URL=http://authentik-server:9000
# Email (Mailpit SMTP)
SMTP_HOST=localhost
SMTP_PORT=1025
SMTP_SECURE=false
# Application
NODE_ENV=development
Für Anwendungen, die in Docker ausgeführt werden und mit anderen Diensten kommunizieren müssen, verwenden Sie Dienstnamen. Verwenden Sie für Anwendungen, die auf Ihrem Hostcomputer ausgeführt werden (NestJS, Next.js im Entwicklungsmodus), localhost mit den vom Host zugeordneten Ports.
Health Checks Deep Dive
Gesundheitsprüfungen verhindern kaskadierende Startfehler. Der depends_on.condition: service_healthy wartet auf die tatsächliche Bereitschaft, nicht nur auf den Start des Containers:
# Without health checks — can fail because PostgreSQL isn't ready
depends_on:
- postgres
# With health checks — waits for PostgreSQL to accept connections
depends_on:
postgres:
condition: service_healthy
Benutzerdefinierte Gesundheitschecks für Ihre eigenen Dienste:
// apps/api/src/health/health.controller.ts
@Get()
@Public()
@HealthCheck()
async check() {
return this.health.check([
() => this.db.isHealthy('database'),
() => this.redis.isHealthy('redis'),
]);
}
# If your API is also dockerized
api:
image: ecosire-api:latest
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3001/api/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
Volumenverwaltung
Benannte Volumes speichern Daten zwischen Neustarts. Verstehen Sie, wann die einzelnen Volume-Typen verwendet werden sollten:
| Geben Sie | ein Beharrlichkeit | Leistung | Verwenden Sie für |
|---|---|---|---|
| Benannter Datenträger | Ja | Ausgezeichnet | Datenbankdaten |
| Bindungshalterung | Ja | Gut (Linux), Schlecht (macOS) | Quellcode Hot-Reload |
| tmpfs | Nein | Ausgezeichnet | Temporäre Dateien, Geheimnisse |
# Use bind mounts for source code (enables hot reload)
volumes:
- ./apps/api/src:/app/src # Code changes reflected immediately
# Use named volumes for data
volumes:
- postgres_data:/var/lib/postgresql/data
# Use tmpfs for ephemeral data
volumes:
- type: tmpfs
target: /tmp
Unter macOS mit Docker Desktop verwenden Bind-Mounts gRPC FUSE, was deutlich langsamer ist als unter Linux. Führen Sie die NestJS- und Next.js-Entwicklungsserver direkt auf Ihrem Hostcomputer (nicht in Docker) aus, um die Leistung des nativen Dateisystems zu erhalten.
Produktion Docker Compose
Die Produktionskompositionsdatei ist strukturell anders – keine lokalen Ports, Neustartrichtlinien, Produktionsressourcenbeschränkungen:
# infrastructure/docker-compose.prod.yml
name: ecosire-prod
services:
postgres:
image: postgres:17-alpine
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
# No port mapping — only accessible within Docker network
restart: always
deploy:
resources:
limits:
memory: 2G
reservations:
memory: 512M
redis:
image: redis:7-alpine
command: redis-server --requirepass ${REDIS_PASSWORD} --maxmemory 1gb
volumes:
- redis_data:/data
restart: always
# No port mapping — internal only
volumes:
postgres_data:
redis_data:
Die Produktion stellt keine Ports extern zur Verfügung – Anwendungen stellen eine Verbindung über das interne Docker-Netzwerk her. Nginx verarbeitet externen Datenverkehr.
Häufige Fallstricke und Lösungen
Falle 1: Portkonflikte mit Systemdiensten
PostgreSQL, Redis und andere Dienste werden häufig als Systemdienste ausgeführt. Ordnen Sie in der Entwicklung immer Nicht-Standard-Ports zu:
- PostgreSQL:
5433:5432(nicht5432:5432) - Redis:
6379:6379behalten (selten Konflikte) - Run
lsof -i :5432to check what's using the default port
Falle 2: Probleme mit der Volume-Berechtigung unter Linux
Docker-Volumes unter Linux verwenden standardmäßig Root-Besitz. Wenn Ihr Containerbenutzer kein Root-Benutzer ist, legen Sie den richtigen Besitz fest:
postgres:
image: postgres:17-alpine
user: "999:999" # postgres user's UID:GID
# Or use init container to fix permissions
Falle 3: Die Authentik-Initialisierung dauert mehr als 60 Sekunden
Authentik führt Datenbankmigrationen beim ersten Start durch. Der start_period: 60s im Gesundheitscheck gibt ihm Zeit. Wenn abhängige Dienste gestartet werden, bevor Authentik bereit ist, schlagen sie fehl. Verwenden Sie die Bedingung service_healthy und geben Sie ihr genügend start_period.
Falle 4: Docker Desktop-Ressourcenbeschränkungen auf dem Mac
Standardmäßig weist Docker Desktop 2 CPUs und 2 GB RAM zu – nicht genug für die gleichzeitige Ausführung von PostgreSQL + Redis + Authentik. Erhöhen Sie die Docker-Desktop-Einstellungen > Ressourcen auf mindestens 4 CPUs und 6 GB RAM.
Falle 5: docker-compose vs. docker compose
Der alte docker-compose (v1, geschrieben in Python) ist veraltet. Verwenden Sie docker compose (v2, das Plugin). Überprüfen Sie Ihre Version: docker compose version. Wenn Sie Docker Compose version v2.x.x sehen, verwenden Sie v2.
Häufig gestellte Fragen
Soll ich meine Anwendungsdienste (NestJS, Next.js) während der Entwicklung in Docker ausführen?
Im Allgemeinen nein – führen Sie bei aktiver Entwicklung Ihre Anwendungsdienste auf Ihrem Host-Computer aus, um das Hot-Reload zu beschleunigen und das Debuggen zu vereinfachen. Verwenden Sie Docker nur für Infrastrukturdienste (Datenbanken, Caches, Identitätsanbieter), die stabil sind und nicht häufig neu gestartet werden müssen. Die Ausnahme besteht, wenn Ihre Anwendung native Abhängigkeiten aufweist, die sich zwischen Ihrem Entwicklungsbetriebssystem und der Produktionsumgebung unterscheiden.
Wie gehe ich mit Datenbankmigrationen im Docker Compose-Workflow um?
Führen Sie Migrationen von Ihrem Hostcomputer aus, nachdem Sie die Infrastruktur gestartet haben: pnpm dev:infra && pnpm db:migrate. Führen Sie während der Entwicklung keine Migrationen innerhalb eines Docker-Containers durch – Sie verlieren die Typprüfung und die IDE-Integration, die Drizzle-Migrationen sicher machen. Verwenden Sie für die erste Datenbankerstellung die initdb.d-Skripte von Docker.
Wie kann ich meine lokalen Docker-Volumes sichern und wiederherstellen?
Verwenden Sie docker run --rm -v postgres_data:/data -v $(pwd):/backup alpine tar czf /backup/postgres-backup.tar.gz /data zum Sichern. Stellen Sie mit dem gleichen Ansatz mit tar xzf wieder her. Für die Entwicklung können Sie auch einen Dump mit pg_dump durchführen und mit psql wiederherstellen, da Sie den Port offengelegt haben.
Wie teile ich den Docker Compose-Status mit anderen Teammitgliedern?
Die Docker Compose-Datei wird über Git geteilt, die Daten in Volumes sind jedoch lokal. Jeder Entwickler beginnt mit einer leeren Datenbank und führt Migrationen/Seeds aus, um diese zu füllen. Verwenden Sie Seed-Skripte (im Repo festgeschrieben), um konsistente Testdaten zu erstellen. Der gemeinsame docker-compose.dev.yml stellt sicher, dass jeder die gleichen Dienstversionen und Konfigurationen verwendet.
Warum Mailpit anstelle von echter E-Mail in der Entwicklung verwenden?
Mailpit ist ein lokaler SMTP-Server, der alle ausgehenden E-Mails erfasst und eine Web-Benutzeroberfläche zur Anzeige dieser E-Mails bereitstellt. Es verhindert, dass während der Entwicklung versehentlich echte E-Mails an echte Benutzer gesendet werden, erfordert keine SMTP-Anmeldeinformationen und ermöglicht die Überprüfung von E-Mail-Vorlagen, ohne Ihren Posteingang zu überprüfen. Konfigurieren Sie Ihre App für die Verwendung von SMTP_HOST=localhost SMTP_PORT=1025 und besuchen Sie http://localhost:8025, um erfasste E-Mails anzuzeigen.
Nächste Schritte
Ein gut ausgearbeitetes Docker Compose-Setup für die lokale Entwicklung ist eine Investition, die sich jedes Mal auszahlt, wenn ein neuer Entwickler hinzukommt oder Sie eine neue Maschine hochfahren. ECOSIRE führt PostgreSQL 17, Redis 7 und Authentik in Docker Compose für die lokale Entwicklung im gesamten Team aus.
Benötigen Sie Hilfe beim Entwurf Ihrer lokalen Entwicklungsinfrastruktur oder beim Containerisieren Ihrer Anwendung für die Produktion? [Entdecken Sie unsere DevOps-Dienste] (/services), um zu sehen, wie wir Ihnen helfen können.
Geschrieben von
ECOSIRE Research and Development Team
Entwicklung von Enterprise-Digitalprodukten bei ECOSIRE. Einblicke in Odoo-Integrationen, E-Commerce-Automatisierung und KI-gestützte Geschäftslösungen.
Verwandte Artikel
AWS EC2 Deployment Guide for Web Applications
Complete AWS EC2 deployment guide: instance selection, security groups, Node.js deployment, Nginx reverse proxy, SSL, auto-scaling, CloudWatch monitoring, and cost optimization.
Cloud vs On-Premise ERP in 2026: The Definitive Guide
Cloud vs on-premise ERP in 2026: total cost analysis, security comparison, scalability, compliance, and the right deployment model for your business.
Building Custom Odoo Modules: Developer Tutorial
Step-by-step tutorial for building custom Odoo 19 modules. Covers module structure, models, views, security, wizards, and best practices for production-ready code.