# =================================================================== # Docker Compose - Basis-Infrastruktur (OPTIMIZED & SECURED) # Meldestelle Project - Essentielle Services # =================================================================== # Security & Performance Improvements: # - Secrets management for sensitive data # - Resource limits and reservations # - Security hardening (read-only volumes, non-root users) # - Optimized health checks and startup # - Production-ready configurations # =================================================================== version: '3.9' services: # =================================================================== # Datenbank - PostgreSQL with Security Hardening # =================================================================== postgres: image: postgres:16-alpine container_name: meldestelle-postgres environment: POSTGRES_USER_FILE: /run/secrets/postgres_user POSTGRES_PASSWORD_FILE: /run/secrets/postgres_password POSTGRES_DB: ${POSTGRES_DB:-meldestelle} # Security hardening POSTGRES_INITDB_ARGS: "--auth-local=trust --auth-host=scram-sha-256" ports: - "5432:5432" volumes: - postgres-data:/var/lib/postgresql/data - ./docker/services/postgres:/docker-entrypoint-initdb.d:ro networks: - meldestelle-network secrets: - postgres_user - postgres_password deploy: resources: limits: cpus: '2.0' memory: 2G reservations: cpus: '0.5' memory: 512M healthcheck: test: ["CMD-SHELL", "pg_isready -U $(cat /run/secrets/postgres_user) -d ${POSTGRES_DB:-meldestelle}"] interval: 10s timeout: 5s retries: 3 start_period: 30s restart: unless-stopped # Security: Run as postgres user (built into image) security_opt: - no-new-privileges:true # =================================================================== # Cache - Redis with Authentication # =================================================================== redis: image: redis:7-alpine container_name: meldestelle-redis ports: - "${REDIS_PORT:-6379}:6379" volumes: - redis-data:/data command: > redis-server --appendonly yes --requirepass "$(cat /run/secrets/redis_password)" --maxmemory 1gb --maxmemory-policy allkeys-lru networks: - meldestelle-network secrets: - redis_password deploy: resources: limits: cpus: '1.0' memory: 1G reservations: cpus: '0.25' memory: 256M healthcheck: test: ["CMD", "redis-cli", "--no-auth-warning", "-a", "$(cat /run/secrets/redis_password)", "ping"] interval: 10s timeout: 5s retries: 3 start_period: 20s restart: unless-stopped security_opt: - no-new-privileges:true # =================================================================== # Authentifizierung - Keycloak with Enhanced Security # =================================================================== keycloak: image: quay.io/keycloak/keycloak:${DOCKER_KEYCLOAK_VERSION:-26.4.0} container_name: meldestelle-keycloak environment: # Admin Configuration - Using secrets KEYCLOAK_ADMIN: admin KEYCLOAK_ADMIN_PASSWORD_FILE: /run/secrets/keycloak_admin_password # Database Configuration - Using secrets KC_DB: postgres KC_DB_URL: jdbc:postgresql://postgres:5432/meldestelle KC_DB_USERNAME_FILE: /run/secrets/postgres_user KC_DB_PASSWORD_FILE: /run/secrets/postgres_password KC_DB_SCHEMA: keycloak # HTTP Configuration - Production settings KC_HTTP_ENABLED: true KC_HOSTNAME_STRICT: false KC_PROXY: edge # Security Configuration KC_FEATURES: token-exchange,admin-fine-grained-authz KC_LOG_LEVEL: ${KEYCLOAK_LOG_LEVEL:-INFO} ports: - "${KEYCLOAK_PORT:-8180}:8080" depends_on: postgres: condition: service_healthy volumes: - ./docker/services/keycloak:/opt/keycloak/data/import:ro - keycloak-data:/opt/keycloak/data command: # Development mode with realm import enabled - start-dev - --import-realm networks: - meldestelle-network secrets: - keycloak_admin_password - postgres_user - postgres_password deploy: resources: limits: cpus: '2.0' memory: 2G reservations: cpus: '0.5' memory: 1G healthcheck: test: ['CMD-SHELL', 'curl -fsS --max-time 5 http://localhost:8080/health/ready || exit 1'] interval: 15s timeout: 10s retries: 5 start_period: 90s restart: unless-stopped security_opt: - no-new-privileges:true # =================================================================== # Service Discovery - Consul # =================================================================== consul: image: hashicorp/consul:1.15 container_name: meldestelle-consul ports: - "${CONSUL_PORT:-8500}:8500" command: > agent -server -ui -node=server-1 -bootstrap-expect=1 -client=0.0.0.0 -encrypt=$(consul keygen) -datacenter=meldestelle-dc networks: - meldestelle-network volumes: - consul-data:/consul/data deploy: resources: limits: cpus: '1.0' memory: 512M reservations: cpus: '0.25' memory: 128M healthcheck: test: ["CMD", "consul", "members", "-http-addr=localhost:8500"] interval: 10s timeout: 5s retries: 3 start_period: 20s restart: unless-stopped security_opt: - no-new-privileges:true # =================================================================== # Messaging - Zookeeper & Kafka with Resource Limits # =================================================================== zookeeper: image: confluentinc/cp-zookeeper:7.4.0 container_name: meldestelle-zookeeper environment: ZOOKEEPER_CLIENT_PORT: ${ZOOKEEPER_CLIENT_PORT:-2181} ZOOKEEPER_TICK_TIME: 2000 ZOOKEEPER_INIT_LIMIT: 5 ZOOKEEPER_SYNC_LIMIT: 2 KAFKA_OPTS: "-Xmx512m -Xms256m" ports: - "${ZOOKEEPER_CLIENT_PORT:-2181}:2181" volumes: - zookeeper-data:/var/lib/zookeeper/data - zookeeper-logs:/var/lib/zookeeper/log networks: - meldestelle-network deploy: resources: limits: cpus: '1.0' memory: 1G reservations: cpus: '0.25' memory: 256M healthcheck: test: ["CMD", "bash", "-c", "echo 'ruok' | nc localhost 2181 | grep imok"] interval: 10s timeout: 5s retries: 3 start_period: 30s restart: unless-stopped security_opt: - no-new-privileges:true kafka: image: confluentinc/cp-kafka:7.4.0 container_name: meldestelle-kafka environment: KAFKA_BROKER_ID: ${KAFKA_BROKER_ID:-1} KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:29092,PLAINTEXT_HOST://localhost:${KAFKA_PORT:-9092} KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: ${KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR:-1} # JVM Settings KAFKA_HEAP_OPTS: "-Xmx1G -Xms512m" KAFKA_JVM_PERFORMANCE_OPTS: "-XX:+UseG1GC -XX:MaxGCPauseMillis=20 -XX:InitiatingHeapOccupancyPercent=35" ports: - "${KAFKA_PORT:-9092}:9092" depends_on: zookeeper: condition: service_healthy volumes: - kafka-data:/var/lib/kafka/data networks: - meldestelle-network deploy: resources: limits: cpus: '2.0' memory: 2G reservations: cpus: '0.5' memory: 512M healthcheck: test: ["CMD", "kafka-broker-api-versions", "--bootstrap-server", "localhost:9092"] interval: 15s timeout: 10s retries: 3 start_period: 60s restart: unless-stopped security_opt: - no-new-privileges:true # =================================================================== # Monitoring - Prometheus with Security # =================================================================== prometheus: image: prom/prometheus:${DOCKER_PROMETHEUS_VERSION:-v2.54.1} container_name: meldestelle-prometheus ports: - "${PROMETHEUS_PORT:-9090}:9090" volumes: - prometheus-data:/prometheus - ./docker/monitoring/prometheus:/etc/prometheus:ro command: - '--config.file=/etc/prometheus/prometheus.yml' - '--storage.tsdb.path=/prometheus' - '--web.console.libraries=/etc/prometheus/console_libraries' - '--web.console.templates=/etc/prometheus/consoles' - '--storage.tsdb.retention.time=15d' - '--storage.tsdb.retention.size=20GB' - '--web.enable-lifecycle' - '--web.enable-admin-api' networks: - meldestelle-network deploy: resources: limits: cpus: '1.0' memory: 2G reservations: cpus: '0.25' memory: 512M healthcheck: test: ["CMD", "promtool", "query", "instant", "localhost:9090", "up"] interval: 15s timeout: 10s retries: 3 start_period: 30s restart: unless-stopped security_opt: - no-new-privileges:true user: "65534:65534" # nobody:nobody # =================================================================== # Monitoring - Grafana with Enhanced Security # =================================================================== grafana: image: grafana/grafana:${DOCKER_GRAFANA_VERSION:-11.3.0} container_name: meldestelle-grafana environment: # Use secrets for admin credentials GF_SECURITY_ADMIN_USER__FILE: /run/secrets/grafana_admin_user GF_SECURITY_ADMIN_PASSWORD__FILE: /run/secrets/grafana_admin_password GF_USERS_ALLOW_SIGN_UP: false GF_INSTALL_PLUGINS: grafana-piechart-panel # Security settings GF_SECURITY_COOKIE_SECURE: true GF_SECURITY_COOKIE_SAMESITE: strict GF_SECURITY_DISABLE_GRAVATAR: true GF_ANALYTICS_REPORTING_ENABLED: false GF_ANALYTICS_CHECK_FOR_UPDATES: false GF_SNAPSHOTS_EXTERNAL_ENABLED: false ports: - "${GRAFANA_PORT:-3000}:3000" volumes: - grafana-data:/var/lib/grafana - ./docker/monitoring/grafana:/etc/grafana/provisioning:ro depends_on: - prometheus networks: - meldestelle-network secrets: - grafana_admin_user - grafana_admin_password deploy: resources: limits: cpus: '1.0' memory: 1G reservations: cpus: '0.25' memory: 256M healthcheck: test: ["CMD", "curl", "--fail", "http://localhost:3000/api/health"] interval: 15s timeout: 10s retries: 3 start_period: 30s restart: unless-stopped security_opt: - no-new-privileges:true user: "472:0" # grafana user # =================================================================== # API Gateway - Enhanced Security & Resource Management # =================================================================== api-gateway: build: context: . dockerfile: dockerfiles/infrastructure/gateway/Dockerfile args: # Global build arguments GRADLE_VERSION: ${DOCKER_GRADLE_VERSION:-9.0.0} JAVA_VERSION: ${DOCKER_JAVA_VERSION:-21} BUILD_DATE: ${BUILD_DATE:-unknown} VERSION: ${DOCKER_APP_VERSION:-1.0.0} # Infrastructure-specific arguments SPRING_PROFILES_ACTIVE: ${DOCKER_SPRING_PROFILES_DEFAULT:-default} container_name: meldestelle-api-gateway volumes: # Mount Gradle cache for better build performance - api-gateway-gradle-cache:/home/gradle/.gradle environment: SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE:-dev,keycloak} CONSUL_HOST: consul CONSUL_PORT: ${CONSUL_PORT:-8500} CONSUL_ENABLED: "true" GATEWAY_PORT: ${GATEWAY_PORT:-8081} # Keycloak OAuth2 Integration - Using internal network KEYCLOAK_SERVER_URL: http://keycloak:8080 KEYCLOAK_ISSUER_URI: http://keycloak:8080/realms/meldestelle KEYCLOAK_JWK_SET_URI: http://keycloak:8080/realms/meldestelle/protocol/openid-connect/certs KEYCLOAK_REALM: meldestelle KEYCLOAK_CLIENT_ID: api-gateway # Security: Client secret via file KEYCLOAK_CLIENT_SECRET_FILE: /run/secrets/keycloak_client_secret # Custom JWT filter disabled - using oauth2ResourceServer instead GATEWAY_SECURITY_KEYCLOAK_ENABLED: "false" # Database connection via secrets DB_HOST: postgres DB_PORT: 5432 DB_NAME: ${POSTGRES_DB:-meldestelle} DB_USERNAME_FILE: /run/secrets/postgres_user DB_PASSWORD_FILE: /run/secrets/postgres_password # Redis connection with auth REDIS_HOST: redis REDIS_PORT: 6379 REDIS_PASSWORD_FILE: /run/secrets/redis_password ports: - "${GATEWAY_PORT:-8081}:8081" depends_on: consul: condition: service_healthy postgres: condition: service_healthy redis: condition: service_healthy keycloak: condition: service_started networks: - meldestelle-network secrets: - keycloak_client_secret - postgres_user - postgres_password - redis_password deploy: resources: limits: cpus: '2.0' memory: 2G reservations: cpus: '0.5' memory: 1G healthcheck: test: ["CMD", "curl", "--fail", "http://localhost:${GATEWAY_PORT:-8081}/actuator/health/readiness"] interval: 15s timeout: 10s retries: 3 start_period: 60s restart: unless-stopped security_opt: - no-new-privileges:true # =================================================================== # Secrets Management - Production Ready # =================================================================== secrets: postgres_user: file: ./docker/secrets/postgres_user.txt postgres_password: file: ./docker/secrets/postgres_password.txt redis_password: file: ./docker/secrets/redis_password.txt keycloak_admin_password: file: ./docker/secrets/keycloak_admin_password.txt keycloak_client_secret: file: ./docker/secrets/keycloak_client_secret.txt grafana_admin_user: file: ./docker/secrets/grafana_admin_user.txt grafana_admin_password: file: ./docker/secrets/grafana_admin_password.txt # =================================================================== # Volumes - Enhanced with Better Drivers # =================================================================== volumes: postgres-data: driver: local driver_opts: type: none o: bind device: ${DATA_PATH:-./data}/postgres redis-data: driver: local driver_opts: type: none o: bind device: ${DATA_PATH:-./data}/redis prometheus-data: driver: local driver_opts: type: none o: bind device: ${DATA_PATH:-./data}/prometheus grafana-data: driver: local driver_opts: type: none o: bind device: ${DATA_PATH:-./data}/grafana keycloak-data: driver: local driver_opts: type: none o: bind device: ${DATA_PATH:-./data}/keycloak consul-data: driver: local driver_opts: type: none o: bind device: ${DATA_PATH:-./data}/consul zookeeper-data: driver: local zookeeper-logs: driver: local kafka-data: driver: local api-gateway-gradle-cache: driver: local # =================================================================== # Networks - Enhanced Security # =================================================================== networks: meldestelle-network: driver: bridge driver_opts: com.docker.network.bridge.enable_icc: "true" com.docker.network.bridge.enable_ip_masquerade: "true" com.docker.network.driver.mtu: "1500" ipam: config: - subnet: 172.20.0.0/16 gateway: 172.20.0.1