1. **Docker-Compose für Entwicklung optimieren** 2. **Umgebungsvariablen für lokale Entwicklung** 3. **Service-Abhängigkeiten** 4. **Docker-Compose für Produktion** 5. **Dokumentation**
441 lines
13 KiB
YAML
441 lines
13 KiB
YAML
version: '3.8'
|
|
|
|
services:
|
|
postgres:
|
|
image: postgres:16-alpine
|
|
environment:
|
|
POSTGRES_USER: ${POSTGRES_USER}
|
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
|
POSTGRES_DB: ${POSTGRES_DB}
|
|
# Production security settings
|
|
POSTGRES_INITDB_ARGS: "--auth-host=scram-sha-256 --auth-local=scram-sha-256"
|
|
ports:
|
|
# Only expose internally, not to host
|
|
- "5432"
|
|
volumes:
|
|
- postgres-data:/var/lib/postgresql/data
|
|
- ./docker/services/postgres:/docker-entrypoint-initdb.d
|
|
# TLS certificates for PostgreSQL
|
|
- ./config/ssl/postgres:/var/lib/postgresql/ssl:ro
|
|
networks:
|
|
- meldestelle-network
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 5
|
|
start_period: 20s
|
|
restart: unless-stopped
|
|
# Security: Run as non-root user
|
|
user: postgres
|
|
# Resource limits
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 1G
|
|
cpus: '0.5'
|
|
reservations:
|
|
memory: 512M
|
|
cpus: '0.25'
|
|
|
|
redis:
|
|
image: redis:7-alpine
|
|
environment:
|
|
# Redis with authentication
|
|
REDIS_PASSWORD: ${REDIS_PASSWORD}
|
|
ports:
|
|
# Only expose internally
|
|
- "6379"
|
|
volumes:
|
|
- redis-data:/data
|
|
- ./config/redis/redis.conf:/usr/local/etc/redis/redis.conf:ro
|
|
# TLS certificates for Redis
|
|
- ./config/ssl/redis:/tls:ro
|
|
command: >
|
|
redis-server /usr/local/etc/redis/redis.conf
|
|
--requirepass ${REDIS_PASSWORD}
|
|
--appendonly yes
|
|
--appendfsync everysec
|
|
--save 900 1
|
|
--save 300 10
|
|
--save 60 10000
|
|
--maxmemory 256mb
|
|
--maxmemory-policy allkeys-lru
|
|
--tcp-keepalive 300
|
|
--timeout 0
|
|
--tcp-backlog 511
|
|
--databases 16
|
|
--stop-writes-on-bgsave-error yes
|
|
--rdbcompression yes
|
|
--rdbchecksum yes
|
|
--dir /data
|
|
networks:
|
|
- meldestelle-network
|
|
healthcheck:
|
|
test: ["CMD", "redis-cli", "--no-auth-warning", "-a", "${REDIS_PASSWORD}", "ping"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 3
|
|
start_period: 10s
|
|
restart: unless-stopped
|
|
# Security: Run as non-root user
|
|
user: redis
|
|
# Resource limits
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 512M
|
|
cpus: '0.25'
|
|
reservations:
|
|
memory: 256M
|
|
cpus: '0.1'
|
|
|
|
keycloak:
|
|
image: quay.io/keycloak/keycloak:23.0
|
|
environment:
|
|
KEYCLOAK_ADMIN: ${KEYCLOAK_ADMIN}
|
|
KEYCLOAK_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD}
|
|
KC_DB: ${KC_DB}
|
|
KC_DB_URL: ${KC_DB_URL}
|
|
KC_DB_USERNAME: ${KC_DB_USERNAME}
|
|
KC_DB_PASSWORD: ${KC_DB_PASSWORD}
|
|
# Production settings
|
|
KC_HOSTNAME: ${KC_HOSTNAME}
|
|
KC_HOSTNAME_STRICT: true
|
|
KC_HOSTNAME_STRICT_HTTPS: true
|
|
KC_HTTP_ENABLED: false
|
|
KC_HTTPS_PORT: 8443
|
|
KC_HTTPS_CERTIFICATE_FILE: /opt/keycloak/conf/server.crt.pem
|
|
KC_HTTPS_CERTIFICATE_KEY_FILE: /opt/keycloak/conf/server.key.pem
|
|
KC_PROXY: edge
|
|
KC_LOG_LEVEL: WARN
|
|
KC_METRICS_ENABLED: true
|
|
KC_HEALTH_ENABLED: true
|
|
# Security headers
|
|
KC_HTTP_RELATIVE_PATH: /auth
|
|
ports:
|
|
# HTTPS only in production
|
|
- "8443:8443"
|
|
depends_on:
|
|
postgres:
|
|
condition: service_healthy
|
|
volumes:
|
|
- ./docker/services/keycloak:/opt/keycloak/data/import
|
|
# TLS certificates
|
|
- ./config/ssl/keycloak:/opt/keycloak/conf:ro
|
|
command: start --import-realm --optimized
|
|
networks:
|
|
- meldestelle-network
|
|
healthcheck:
|
|
test: ["CMD", "curl", "--fail", "--insecure", "https://localhost:8443/auth/health/ready"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 5
|
|
start_period: 60s
|
|
restart: unless-stopped
|
|
# Resource limits
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 1G
|
|
cpus: '0.5'
|
|
reservations:
|
|
memory: 512M
|
|
cpus: '0.25'
|
|
|
|
zookeeper:
|
|
image: confluentinc/cp-zookeeper:7.5.0
|
|
environment:
|
|
ZOOKEEPER_CLIENT_PORT: ${ZOOKEEPER_CLIENT_PORT}
|
|
ZOOKEEPER_TICK_TIME: 2000
|
|
ZOOKEEPER_SYNC_LIMIT: 2
|
|
ZOOKEEPER_INIT_LIMIT: 5
|
|
ZOOKEEPER_MAX_CLIENT_CNXNS: 60
|
|
ZOOKEEPER_AUTOPURGE_SNAP_RETAIN_COUNT: 3
|
|
ZOOKEEPER_AUTOPURGE_PURGE_INTERVAL: 24
|
|
# Security settings
|
|
ZOOKEEPER_AUTH_PROVIDER_SASL: org.apache.zookeeper.server.auth.SASLAuthenticationProvider
|
|
ZOOKEEPER_REQUIRE_CLIENT_AUTH_SCHEME: sasl
|
|
KAFKA_OPTS: "-Djava.security.auth.login.config=/etc/kafka/secrets/zookeeper_jaas.conf"
|
|
ports:
|
|
# Only expose internally
|
|
- "2181"
|
|
volumes:
|
|
- zookeeper-data:/var/lib/zookeeper/data
|
|
- zookeeper-logs:/var/lib/zookeeper/log
|
|
- ./config/kafka/secrets:/etc/kafka/secrets:ro
|
|
networks:
|
|
- meldestelle-network
|
|
healthcheck:
|
|
test: ["CMD", "nc", "-z", "localhost", "2181"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 3
|
|
start_period: 10s
|
|
restart: unless-stopped
|
|
# Resource limits
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 512M
|
|
cpus: '0.25'
|
|
reservations:
|
|
memory: 256M
|
|
cpus: '0.1'
|
|
|
|
kafka:
|
|
image: confluentinc/cp-kafka:7.5.0
|
|
depends_on:
|
|
zookeeper:
|
|
condition: service_healthy
|
|
ports:
|
|
# Only expose internally
|
|
- "9092"
|
|
- "9093"
|
|
environment:
|
|
KAFKA_BROKER_ID: ${KAFKA_BROKER_ID}
|
|
KAFKA_ZOOKEEPER_CONNECT: ${KAFKA_ZOOKEEPER_CONNECT}
|
|
# Production security settings with SASL/SSL
|
|
KAFKA_ADVERTISED_LISTENERS: SASL_SSL://kafka:9093,SASL_PLAINTEXT://kafka:9092
|
|
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: SASL_SSL:SASL_SSL,SASL_PLAINTEXT:SASL_PLAINTEXT
|
|
KAFKA_INTER_BROKER_LISTENER_NAME: SASL_SSL
|
|
KAFKA_SECURITY_INTER_BROKER_PROTOCOL: SASL_SSL
|
|
KAFKA_SASL_MECHANISM_INTER_BROKER_PROTOCOL: PLAIN
|
|
KAFKA_SASL_ENABLED_MECHANISMS: PLAIN
|
|
# SSL Configuration
|
|
KAFKA_SSL_KEYSTORE_FILENAME: kafka.server.keystore.jks
|
|
KAFKA_SSL_KEYSTORE_CREDENTIALS: kafka_keystore_creds
|
|
KAFKA_SSL_KEY_CREDENTIALS: kafka_ssl_key_creds
|
|
KAFKA_SSL_TRUSTSTORE_FILENAME: kafka.server.truststore.jks
|
|
KAFKA_SSL_TRUSTSTORE_CREDENTIALS: kafka_truststore_creds
|
|
KAFKA_SSL_CLIENT_AUTH: required
|
|
# Performance and reliability settings
|
|
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 3
|
|
KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 3
|
|
KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 2
|
|
KAFKA_DEFAULT_REPLICATION_FACTOR: 3
|
|
KAFKA_MIN_INSYNC_REPLICAS: 2
|
|
KAFKA_NUM_PARTITIONS: 3
|
|
KAFKA_LOG_RETENTION_HOURS: 168
|
|
KAFKA_LOG_SEGMENT_BYTES: 1073741824
|
|
KAFKA_LOG_RETENTION_CHECK_INTERVAL_MS: 300000
|
|
KAFKA_AUTO_CREATE_TOPICS_ENABLE: false
|
|
# JVM settings
|
|
KAFKA_HEAP_OPTS: "-Xmx512M -Xms512M"
|
|
KAFKA_JVM_PERFORMANCE_OPTS: "-server -XX:+UseG1GC -XX:MaxGCPauseMillis=20 -XX:InitiatingHeapOccupancyPercent=35"
|
|
# Security
|
|
KAFKA_OPTS: "-Djava.security.auth.login.config=/etc/kafka/secrets/kafka_jaas.conf"
|
|
volumes:
|
|
- kafka-data:/var/lib/kafka/data
|
|
- ./config/kafka/secrets:/etc/kafka/secrets:ro
|
|
networks:
|
|
- meldestelle-network
|
|
healthcheck:
|
|
test: ["CMD", "kafka-topics", "--bootstrap-server", "localhost:9092", "--list"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 3
|
|
start_period: 30s
|
|
restart: unless-stopped
|
|
# Resource limits
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 1G
|
|
cpus: '0.5'
|
|
reservations:
|
|
memory: 512M
|
|
cpus: '0.25'
|
|
|
|
zipkin:
|
|
image: openzipkin/zipkin:2
|
|
ports:
|
|
# Only expose internally
|
|
- "9411"
|
|
environment:
|
|
# Production settings
|
|
JAVA_OPTS: "-Xms256m -Xmx512m"
|
|
STORAGE_TYPE: elasticsearch
|
|
ES_HOSTS: http://elasticsearch:9200
|
|
networks:
|
|
- meldestelle-network
|
|
healthcheck:
|
|
test: ["CMD", "wget", "-q", "-O", "-", "http://localhost:9411/health"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 3
|
|
start_period: 15s
|
|
restart: unless-stopped
|
|
# Resource limits
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 512M
|
|
cpus: '0.25'
|
|
reservations:
|
|
memory: 256M
|
|
cpus: '0.1'
|
|
|
|
# Production monitoring services
|
|
prometheus:
|
|
image: prom/prometheus:latest
|
|
volumes:
|
|
- ./config/monitoring/prometheus.prod.yml:/etc/prometheus/prometheus.yml:ro
|
|
- prometheus-data:/prometheus
|
|
# TLS certificates
|
|
- ./config/ssl/prometheus:/etc/ssl/prometheus:ro
|
|
command:
|
|
- '--config.file=/etc/prometheus/prometheus.yml'
|
|
- '--storage.tsdb.path=/prometheus'
|
|
- '--storage.tsdb.retention.time=30d'
|
|
- '--storage.tsdb.retention.size=10GB'
|
|
- '--web.console.libraries=/etc/prometheus/console_libraries'
|
|
- '--web.console.templates=/etc/prometheus/consoles'
|
|
- '--web.enable-lifecycle'
|
|
- '--web.enable-admin-api'
|
|
- '--web.external-url=https://${PROMETHEUS_HOSTNAME}'
|
|
- '--web.config.file=/etc/ssl/prometheus/web.yml'
|
|
ports:
|
|
# Only expose internally
|
|
- "9090"
|
|
networks:
|
|
- meldestelle-network
|
|
healthcheck:
|
|
test: ["CMD", "wget", "-q", "-O", "-", "http://localhost:9090/-/healthy"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 3
|
|
start_period: 15s
|
|
restart: unless-stopped
|
|
# Security: Run as non-root user
|
|
user: "65534:65534"
|
|
# Resource limits
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 1G
|
|
cpus: '0.5'
|
|
reservations:
|
|
memory: 512M
|
|
cpus: '0.25'
|
|
|
|
grafana:
|
|
image: grafana/grafana:latest
|
|
volumes:
|
|
- ./config/monitoring/grafana/provisioning:/etc/grafana/provisioning:ro
|
|
- ./config/monitoring/grafana/dashboards:/var/lib/grafana/dashboards:ro
|
|
- grafana-data:/var/lib/grafana
|
|
# TLS certificates
|
|
- ./config/ssl/grafana:/etc/ssl/grafana:ro
|
|
environment:
|
|
# Security settings
|
|
- GF_SECURITY_ADMIN_USER=${GF_SECURITY_ADMIN_USER}
|
|
- GF_SECURITY_ADMIN_PASSWORD=${GF_SECURITY_ADMIN_PASSWORD}
|
|
- GF_USERS_ALLOW_SIGN_UP=false
|
|
- GF_USERS_ALLOW_ORG_CREATE=false
|
|
- GF_AUTH_ANONYMOUS_ENABLED=false
|
|
# HTTPS settings
|
|
- GF_SERVER_PROTOCOL=https
|
|
- GF_SERVER_CERT_FILE=/etc/ssl/grafana/server.crt
|
|
- GF_SERVER_CERT_KEY=/etc/ssl/grafana/server.key
|
|
- GF_SERVER_DOMAIN=${GRAFANA_HOSTNAME}
|
|
- GF_SERVER_ROOT_URL=https://${GRAFANA_HOSTNAME}
|
|
# Security headers
|
|
- GF_SECURITY_STRICT_TRANSPORT_SECURITY=true
|
|
- GF_SECURITY_STRICT_TRANSPORT_SECURITY_MAX_AGE_SECONDS=31536000
|
|
- GF_SECURITY_CONTENT_TYPE_PROTECTION=true
|
|
- GF_SECURITY_X_CONTENT_TYPE_OPTIONS=nosniff
|
|
- GF_SECURITY_X_XSS_PROTECTION=true
|
|
# Session settings
|
|
- GF_SESSION_PROVIDER=redis
|
|
- GF_SESSION_PROVIDER_CONFIG=addr=redis:6379,pool_size=100,db=2,password=${REDIS_PASSWORD}
|
|
- GF_SESSION_COOKIE_SECURE=true
|
|
- GF_SESSION_COOKIE_SAMESITE=strict
|
|
# Logging
|
|
- GF_LOG_MODE=console
|
|
- GF_LOG_LEVEL=warn
|
|
ports:
|
|
# HTTPS only
|
|
- "3443:3000"
|
|
networks:
|
|
- meldestelle-network
|
|
depends_on:
|
|
prometheus:
|
|
condition: service_healthy
|
|
redis:
|
|
condition: service_healthy
|
|
healthcheck:
|
|
test: ["CMD", "wget", "-q", "-O", "-", "--no-check-certificate", "https://localhost:3000/api/health"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 3
|
|
start_period: 20s
|
|
restart: unless-stopped
|
|
# Security: Run as non-root user
|
|
user: "472:472"
|
|
# Resource limits
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 512M
|
|
cpus: '0.25'
|
|
reservations:
|
|
memory: 256M
|
|
cpus: '0.1'
|
|
|
|
# Reverse proxy for production
|
|
nginx:
|
|
image: nginx:alpine
|
|
ports:
|
|
- "80:80"
|
|
- "443:443"
|
|
volumes:
|
|
- ./config/nginx/nginx.prod.conf:/etc/nginx/nginx.conf:ro
|
|
- ./config/nginx/conf.d:/etc/nginx/conf.d:ro
|
|
- ./config/ssl/nginx:/etc/ssl/nginx:ro
|
|
- ./logs/nginx:/var/log/nginx
|
|
networks:
|
|
- meldestelle-network
|
|
depends_on:
|
|
- keycloak
|
|
- grafana
|
|
healthcheck:
|
|
test: ["CMD", "wget", "-q", "-O", "-", "http://localhost/health"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 3
|
|
start_period: 10s
|
|
restart: unless-stopped
|
|
# Resource limits
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 256M
|
|
cpus: '0.1'
|
|
reservations:
|
|
memory: 128M
|
|
cpus: '0.05'
|
|
|
|
volumes:
|
|
postgres-data:
|
|
driver: local
|
|
redis-data:
|
|
driver: local
|
|
kafka-data:
|
|
driver: local
|
|
zookeeper-data:
|
|
driver: local
|
|
zookeeper-logs:
|
|
driver: local
|
|
prometheus-data:
|
|
driver: local
|
|
grafana-data:
|
|
driver: local
|
|
|
|
networks:
|
|
meldestelle-network:
|
|
driver: bridge
|
|
ipam:
|
|
config:
|
|
- subnet: 172.20.0.0/16
|