name: "${PROJECT_NAME:-meldestelle}" services: # ========================================== # CORE INFRASTRUCTURE # ========================================== # --- DATABASE: PostgreSQL --- postgres: image: "${POSTGRES_IMAGE:-postgres:16-alpine}" container_name: "${PROJECT_NAME:-meldestelle}-postgres" restart: "${RESTART_POLICY:-no}" ports: - "${POSTGRES_PORT:-5432:5432}" environment: POSTGRES_USER: "${POSTGRES_USER:-pg-user}" POSTGRES_PASSWORD: "${POSTGRES_PASSWORD:-pg-password}" POSTGRES_DB: "${POSTGRES_DB:-pg-meldestelle-db}" volumes: - "postgres-data:/var/lib/postgresql/data" - "./config/docker/postgres:/docker-entrypoint-initdb.d:Z" - "./config/docker/postgres/postgresql.conf:/etc/postgresql/postgresql.conf:Z" profiles: [ "infra", "all" ] command: [ "postgres", "-c", "config_file=/etc/postgresql/postgresql.conf" ] healthcheck: test: [ "CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}" ] interval: "5s" timeout: "5s" retries: "5" start_period: "10s" networks: meldestelle-network: aliases: - "postgres" # --- CACHE: Redis --- redis: image: "${REDIS_IMAGE:-redis:7.4-alpine}" container_name: "${PROJECT_NAME:-meldestelle}-redis" restart: "${RESTART_POLICY:-no}" ports: - "${REDIS_PORT:-6379:6379}" volumes: - "redis-data:/data" - "./config/docker/redis/redis.conf:/usr/local/etc/redis/redis.conf:Z" profiles: [ "infra", "all" ] command: [ "sh", "-lc", "exec redis-server /usr/local/etc/redis/redis.conf --protected-mode no ${REDIS_PASSWORD:+--requirepass $REDIS_PASSWORD}" ] healthcheck: test: [ "CMD-SHELL", "[ -z \"$REDIS_PASSWORD\" ] && redis-cli ping | grep PONG || redis-cli -a \"$REDIS_PASSWORD\" ping | grep PONG" ] interval: "5s" timeout: "5s" retries: "3" networks: meldestelle-network: aliases: - "redis" # --- IAM: Keycloak --- keycloak: image: "meldestelle-keycloak:latest" container_name: "${PROJECT_NAME:-meldestelle}-keycloak" restart: "${RESTART_POLICY:-no}" build: context: "./config/docker/keycloak" args: KEYCLOAK_IMAGE_TAG: "${KEYCLOAK_IMAGE_TAG:-26.4}" BUILD_DATE: "${DOCKER_BUILD_DATE}" labels: - "org.opencontainers.image.created=${DOCKER_BUILD_DATE}" environment: KC_BOOTSTRAP_ADMIN_USERNAME: "${KC_ADMIN_USERNAME:-kc-admin}" KC_BOOTSTRAP_ADMIN_PASSWORD: "${KC_ADMIN_PASSWORD:-kc-password}" KC_DB: "${KC_DB:-postgres}" KC_DB_SCHEMA: "${KC_DB_SCHEMA:-keycloak}" KC_DB_URL: "${POSTGRES_DB_URL:-jdbc:postgresql://postgres:5432/pg-meldestelle-db}" KC_DB_USERNAME: "${POSTGRES_USER:-pg-user}" KC_DB_PASSWORD: "${POSTGRES_PASSWORD:-pg-password}" KC_HOSTNAME: "${KC_HOSTNAME:-localhost}" KC_HTTP_ENABLED: "true" KC_PROXY_HEADERS: "xforwarded" KC_HEALTH_ENABLED: "true" KC_METRICS_ENABLED: "true" ports: - "${KC_PORT:-8180:8080}" - "${KC_DEBUG_PORT:-9000:9000}" depends_on: postgres: condition: "service_healthy" redis: condition: "service_healthy" volumes: - "./config/docker/keycloak:/opt/keycloak/data/import:Z" profiles: [ "infra", "all" ] command: "start --optimized --import-realm" healthcheck: test: [ "CMD-SHELL", "exec 3<>/dev/tcp/127.0.0.1/9000" ] interval: "10s" timeout: "5s" retries: "5" start_period: "60s" networks: meldestelle-network: aliases: - "keycloak" # --- DATENBANK-MANAGEMENT-TOOL: pgAdmin4 --- pgadmin: image: "${PGADMIN_IMAGE:-dpage/pgadmin4:8}" container_name: "${PROJECT_NAME:-meldestelle}-pgadmin" restart: "${RESTART_POLICY:-no}" ports: - "${PGADMIN_PORT:-8888:80}" environment: PGADMIN_DEFAULT_EMAIL: "${PGADMIN_EMAIL:-meldestelle@mo-code.at}" PGADMIN_DEFAULT_PASSWORD: "${PGADMIN_PASSWORD:-pgadmin}" volumes: - "pgadmin-data:/var/lib/pgadmin" profiles: [ "tools", "all" ] networks: meldestelle-network: aliases: - "pgadmin" # --- MONITORING: Postgres Exporter --- postgres-exporter: image: "${POSTGRES_EXPORTER_IMAGE:-prometheuscommunity/postgres-exporter:v0.18.0}" container_name: "${PROJECT_NAME:-meldestelle}-postgres-exporter" restart: "${RESTART_POLICY:-no}" environment: DATA_SOURCE_NAME: "postgresql://${POSTGRES_USER:-pg-user}:${POSTGRES_PASSWORD:-pg-password}@postgres:5432/${POSTGRES_DB:-pg-meldestelle-db}?sslmode=disable" depends_on: postgres: condition: "service_healthy" networks: meldestelle-network: aliases: - "postgres-exporter" profiles: [ "ops", "all" ] # --- MONITORING: Alertmanager --- alertmanager: image: "${ALERTMANAGER_IMAGE:-prom/alertmanager:v0.29.0}" container_name: "${PROJECT_NAME:-meldestelle}-alertmanager" restart: "${RESTART_POLICY:-no}" ports: - "${ALERTMANAGER_PORT:-9093:9093}" volumes: # Wir müssen hier envsubst nutzen ODER die Config ohne Variablen schreiben. # Einfachste Lösung: Ein Entrypoint-Script, das envsubst macht (ähnlich wie bei Nginx). # ODER: Wir hardcoden es für Dev erst mal. - ./config/docker/monitoring/alertmanager/alertmanager.yaml:/etc/alertmanager/alertmanager.yaml command: - --config.file=/etc/alertmanager/alertmanager.yaml profiles: [ "ops", "all" ] networks: meldestelle-network: aliases: - "alertmanager" # --- MONITORING: Prometheus --- prometheus: image: "${PROMETHEUS_IMAGE:-prom/prometheus:v3.7.3}" container_name: "${PROJECT_NAME:-meldestelle}-prometheus" restart: "${RESTART_POLICY:-no}" ports: - "${PROMETHEUS_PORT:-9090:9090}" volumes: - "prometheus-data:/prometheus" - "./config/docker/monitoring/prometheus:/etc/prometheus:Z" - "./config/docker/monitoring/prometheus/rules:/etc/prometheus/rules:Z" command: - --web.enable-lifecycle - --config.file=/etc/prometheus/prometheus.yaml - --storage.tsdb.retention.time=15d healthcheck: test: [ "CMD", "wget", "--spider", "-q", "http://localhost:9090/-/healthy" ] interval: "30s" timeout: "10s" retries: "3" start_period: "30s" networks: meldestelle-network: aliases: - "prometheus" profiles: [ "ops", "all" ] # --- MONITORING: Grafana --- grafana: image: "${GF_IMAGE:-grafana/grafana:12.3}" container_name: "${PROJECT_NAME:-meldestelle}-grafana" restart: "${RESTART_POLICY:-no}" environment: GF_SECURITY_ADMIN_USER: "${GF_ADMIN_USER:-gf-admin}" GF_SECURITY_ADMIN_PASSWORD: "${GF_ADMIN_PASSWORD:-gf-password}" ports: - "${GF_PORT:-3000:3000}" volumes: - grafana-data:/var/lib/grafana # Provisioning (datasources/dashboards) from central config - ./config/docker/monitoring/grafana/provisioning:/etc/grafana/provisioning:Z # Dashboards directory (referenced by a provisioning file path: /var/lib/grafana/dashboards) - ./config/docker/monitoring/grafana/dashboards:/var/lib/grafana/dashboards:Z depends_on: prometheus: condition: "service_healthy" healthcheck: test: [ "CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/api/health" ] interval: "30s" timeout: "10s" retries: "3" start_period: "30s" networks: meldestelle-network: aliases: - "grafana" profiles: [ "ops", "all" ] # --- CONSUL --- consul: image: "${CONSUL_IMAGE:-hashicorp/consul:1.22.1}" container_name: "${PROJECT_NAME:-meldestelle}-consul" restart: "${RESTART_POLICY:-no}" ports: - "${CONSUL_PORT:-8500:8500}" - "${CONSUL_UDP_PORT:-8600:8600/udp}" command: "agent -server -bootstrap-expect=1 -ui -client=0.0.0.0" healthcheck: test: [ "CMD", "curl", "-f", "http://localhost:8500/v1/status/leader" ] interval: "30s" timeout: "10s" retries: "3" networks: meldestelle-network: aliases: - "consul" profiles: [ "infra", "all" ] # --- API-GATEWAY: Spring Cloud Gateway --- api-gateway: build: context: . dockerfile: backend/infrastructure/gateway/Dockerfile args: # Build-Args aus deinen .env Dateien (werden hier statisch benötigt für den Build) GRADLE_VERSION: "${DOCKER_GRADLE_VERSION:-9.2.1}" JAVA_VERSION: "${DOCKER_JAVA_VERSION:-25}" VERSION: "${DOCKER_VERSION:-1.0.0-SNAPSHOT}" BUILD_DATE: "${DOCKER_BUILD_DATE}" labels: - "org.opencontainers.image.created=${DOCKER_BUILD_DATE}" container_name: "${PROJECT_NAME:-meldestelle}-gateway" restart: "${RESTART_POLICY:-no}" ports: - "${GATEWAY_PORT:-8081:8081}" - "${GATEWAY_DEBUG_PORT:-5005:5005}" environment: # server.port must be an integer. Do not pass host:container mapping here. SERVER_PORT: "${GATEWAY_SERVER_PORT:-8081}" SPRING_PROFILES_ACTIVE: "${GATEWAY_SPRING_PROFILES_ACTIVE:-docker}" DEBUG: "${GATEWAY_DEBUG:-true}" # --- KEYCLOAK --- # Container-zu-Container Kommunikation (intern) SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI: "${SSEC_ISSUER_URI:-http://keycloak:8080/realms/meldestelle}" # JWK Set Uri erzwingen, damit er nicht über den Issuer (localhost vs keycloak) stolpert SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI: "${SSEC_JWK_SET_URI:-http://keycloak:8080/realms/meldestelle/protocol/openid-connect/certs}" # --- CONSUL --- SPRING_CLOUD_CONSUL_HOST: "${CONSUL_HOST:-consul}" # Consul port must be an integer (container internal port) SPRING_CLOUD_CONSUL_PORT: "${CONSUL_HTTP_PORT:-8500}" # Registrierungsname in Consul SPRING_CLOUD_CONSUL_DISCOVERY_SERVICE_NAME: "${GATEWAY_SERVICE_NAME:-api-gateway}" # Wichtig für Docker: Wir wollen IP-Adressen registrieren, keine Hostnames, die DNS brauchen SPRING_CLOUD_CONSUL_DISCOVERY_PREFER_IP_ADDRESS: "${GATEWAY_CONSUL_PREFER_IP:-true}" # --- POSTGRES --- SPRING_DATASOURCE_URL: "${POSTGRES_DB_URL:-jdbc:postgresql://postgres:5432/pg-meldestelle-db}" SPRING_DATASOURCE_USERNAME: "${POSTGRES_USER:-pg-user}" SPRING_DATASOURCE_PASSWORD: "${POSTGRES_PASSWORD:-pg-password}" # --- REDIS --- SPRING_DATA_REDIS_HOST: "${REDIS_SERVER_HOSTNAME:-redis}" SPRING_DATA_REDIS_PORT: "${REDIS_SERVICE_PORT:-6379}" SPRING_DATA_REDIS_PASSWORD: "${REDIS_PASSWORD:-redis-password}" SPRING_DATA_REDIS_CONNECT_TIMEOUT: "${REDIS_SERVER_CONNECT_TIMEOUT:-5s}" # --- LOGGING --- LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_GATEWAY: "DEBUG" LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_SECURITY: "DEBUG" depends_on: postgres: condition: "service_healthy" keycloak: condition: "service_healthy" consul: condition: "service_healthy" redis: condition: "service_healthy" networks: meldestelle-network: aliases: - "api-gateway" profiles: [ "backend", "all" ] volumes: - ./config/app/base-application.yaml:/workspace/config/application.yml:Z # ========================================== # MICROSERVICES # ========================================== ping-service: build: context: . dockerfile: backend/services/ping/Dockerfile args: # Build-Args aus deinen .env Dateien (werden hier statisch benötigt für den Build) GRADLE_VERSION: "${DOCKER_GRADLE_VERSION:-9.2.1}" JAVA_VERSION: "${DOCKER_JAVA_VERSION:-25}" VERSION: "${DOCKER_VERSION:-1.0.0-SNAPSHOT}" BUILD_DATE: "${DOCKER_BUILD_DATE}" labels: - "org.opencontainers.image.created=${DOCKER_BUILD_DATE}" container_name: "${PROJECT_NAME:-meldestelle}-ping-service" restart: "${RESTART_POLICY:-no}" ports: - "${PING_PORT:-8082:8082}" - "${PING_DEBUG_PORT:-5006:5006}" environment: SPRING_PROFILES_ACTIVE: "${PING_SPRING_PROFILES_ACTIVE:-docker}" DEBUG: "${PING_DEBUG:-true}" SERVER_PORT: "${PING_SERVER_PORT:-8082}" # --- CONSUL --- SPRING_CLOUD_CONSUL_HOST: "${CONSUL_HOST:-consul}" SPRING_CLOUD_CONSUL_PORT: "${CONSUL_HTTP_PORT:-8500}" SPRING_CLOUD_CONSUL_DISCOVERY_SERVICE_NAME: "${PING_SERVICE_NAME:-ping-service}" SPRING_CLOUD_CONSUL_DISCOVERY_PREFER_IP_ADDRESS: "${PING_CONSUL_PREFER_IP:-true}" # - DATENBANK VERBINDUNG - SPRING_DATASOURCE_URL: "${POSTGRES_DB_URL:-jdbc:postgresql://postgres:5432/pg-meldestelle-db}" SPRING_DATASOURCE_USERNAME: "${POSTGRES_USER:-pg-user}" SPRING_DATASOURCE_PASSWORD: "${POSTGRES_PASSWORD:-pg-password}" # --- REDIS --- SPRING_DATA_REDIS_HOST: "${REDIS_SERVER_HOSTNAME:-redis}" SPRING_DATA_REDIS_PORT: "${REDIS_SERVICE_PORT:-6379}" SPRING_DATA_REDIS_PASSWORD: "${REDIS_PASSWORD:-redis-password}" SPRING_DATA_REDIS_CONNECT_TIMEOUT: "${REDIS_SERVER_CONNECT_TIMEOUT:-5s}" depends_on: postgres: condition: "service_healthy" keycloak: condition: "service_healthy" consul: condition: "service_healthy" redis: condition: "service_healthy" networks: meldestelle-network: aliases: - "ping-service" profiles: [ "backend", "all" ] volumes: - ./config/app/base-application.yaml:/workspace/config/application.yml:Z entries-service: build: context: . dockerfile: backend/services/entries/Dockerfile args: GRADLE_VERSION: "${DOCKER_GRADLE_VERSION:-9.2.1}" JAVA_VERSION: "${DOCKER_JAVA_VERSION:-25}" VERSION: "${DOCKER_VERSION:-1.0.0-SNAPSHOT}" BUILD_DATE: "${DOCKER_BUILD_DATE}" labels: - "org.opencontainers.image.created=${DOCKER_BUILD_DATE}" container_name: "${PROJECT_NAME:-meldestelle}-entries-service" restart: "${RESTART_POLICY:-no}" ports: - "8083:8083" environment: SERVER_PORT: "8083" SPRING_PROFILES_ACTIVE: "${SPRING_PROFILES_ACTIVE:-docker}" SPRING_CLOUD_CONSUL_HOST: "${CONSUL_HOST:-consul}" SPRING_CLOUD_CONSUL_PORT: "${CONSUL_HTTP_PORT:-8500}" SPRING_CLOUD_CONSUL_DISCOVERY_SERVICE_NAME: "entries-service" SPRING_CLOUD_CONSUL_DISCOVERY_PREFER_IP_ADDRESS: "true" SPRING_DATASOURCE_URL: "${POSTGRES_DB_URL:-jdbc:postgresql://postgres:5432/pg-meldestelle-db}" SPRING_DATASOURCE_USERNAME: "${POSTGRES_USER:-pg-user}" SPRING_DATASOURCE_PASSWORD: "${POSTGRES_PASSWORD:-pg-password}" depends_on: postgres: condition: "service_healthy" consul: condition: "service_healthy" networks: meldestelle-network: aliases: - "entries-service" profiles: [ "backend", "all" ] volumes: - ./config/app/base-application.yaml:/workspace/config/application.yml:Z results-service: build: context: . dockerfile: backend/services/results/results-service/Dockerfile args: GRADLE_VERSION: "${DOCKER_GRADLE_VERSION:-9.2.1}" JAVA_VERSION: "${DOCKER_JAVA_VERSION:-25}" VERSION: "${DOCKER_VERSION:-1.0.0-SNAPSHOT}" BUILD_DATE: "${DOCKER_BUILD_DATE}" labels: - "org.opencontainers.image.created=${DOCKER_BUILD_DATE}" container_name: "${PROJECT_NAME:-meldestelle}-results-service" restart: "${RESTART_POLICY:-no}" ports: - "8084:8084" environment: SERVER_PORT: "8084" SPRING_PROFILES_ACTIVE: "${SPRING_PROFILES_ACTIVE:-docker}" SPRING_CLOUD_CONSUL_HOST: "${CONSUL_HOST:-consul}" SPRING_CLOUD_CONSUL_PORT: "${CONSUL_HTTP_PORT:-8500}" SPRING_CLOUD_CONSUL_DISCOVERY_SERVICE_NAME: "results-service" SPRING_CLOUD_CONSUL_DISCOVERY_PREFER_IP_ADDRESS: "true" SPRING_DATASOURCE_URL: "${POSTGRES_DB_URL:-jdbc:postgresql://postgres:5432/pg-meldestelle-db}" SPRING_DATASOURCE_USERNAME: "${POSTGRES_USER:-pg-user}" SPRING_DATASOURCE_PASSWORD: "${POSTGRES_PASSWORD:-pg-password}" depends_on: postgres: condition: "service_healthy" consul: condition: "service_healthy" networks: meldestelle-network: aliases: - "results-service" profiles: [ "backend", "all" ] volumes: - ./config/app/base-application.yaml:/workspace/config/application.yml:Z scheduling-service: build: context: . dockerfile: backend/services/scheduling/scheduling-service/Dockerfile args: GRADLE_VERSION: "${DOCKER_GRADLE_VERSION:-9.2.1}" JAVA_VERSION: "${DOCKER_JAVA_VERSION:-25}" VERSION: "${DOCKER_VERSION:-1.0.0-SNAPSHOT}" BUILD_DATE: "${DOCKER_BUILD_DATE}" labels: - "org.opencontainers.image.created=${DOCKER_BUILD_DATE}" container_name: "${PROJECT_NAME:-meldestelle}-scheduling-service" restart: "${RESTART_POLICY:-no}" ports: - "8085:8085" environment: SERVER_PORT: "8085" SPRING_PROFILES_ACTIVE: "${SPRING_PROFILES_ACTIVE:-docker}" SPRING_CLOUD_CONSUL_HOST: "${CONSUL_HOST:-consul}" SPRING_CLOUD_CONSUL_PORT: "${CONSUL_HTTP_PORT:-8500}" SPRING_CLOUD_CONSUL_DISCOVERY_SERVICE_NAME: "scheduling-service" SPRING_CLOUD_CONSUL_DISCOVERY_PREFER_IP_ADDRESS: "true" SPRING_DATASOURCE_URL: "${POSTGRES_DB_URL:-jdbc:postgresql://postgres:5432/pg-meldestelle-db}" SPRING_DATASOURCE_USERNAME: "${POSTGRES_USER:-pg-user}" SPRING_DATASOURCE_PASSWORD: "${POSTGRES_PASSWORD:-pg-password}" depends_on: postgres: condition: "service_healthy" consul: condition: "service_healthy" networks: meldestelle-network: aliases: - "scheduling-service" profiles: [ "backend", "all" ] volumes: - ./config/app/base-application.yaml:/workspace/config/application.yml:Z # ========================================== # FRONTEND # ========================================== # --- WEB-APP --- web-app: build: context: . # Wichtig: Root Context für Monorepo Zugriff dockerfile: config/docker/nginx/web-app/Dockerfile args: GRADLE_VERSION: "${DOCKER_GRADLE_VERSION:-9.2.1}" JAVA_VERSION: "${DOCKER_JAVA_VERSION:-25}" # Frontend spezifisch: NODE_VERSION: "${DOCKER_NODE_VERSION:-24.12.0}" NGINX_IMAGE_TAG: "${DOCKER_NGINX_VERSION:-1.28.0-alpine}" WEB_BUILD_PROFILE: "${WEB_BUILD_PROFILE:-dev}" # dev oder prod # Metadaten: VERSION: "${DOCKER_VERSION:-1.0.0-SNAPSHOT}" BUILD_DATE: "${DOCKER_BUILD_DATE}" labels: - "org.opencontainers.image.created=${DOCKER_BUILD_DATE}" container_name: "${PROJECT_NAME:-meldestelle}-web-app" restart: "${RESTART_POLICY:-no}" ports: - "${WEB_APP_PORT:-4000:4000}" environment: # Nginx braucht eigentlich keine Env-Vars zur Laufzeit für die Config, # außer man nutzt envsubst im Entrypoint (für dynamische API URLs). # Hier nutzen wir den Docker-DNS Namen 'api-gateway', der ist fest in nginx.conf. dummy_var: "prevent_empty_block" # volumes: # # Hot-Reloading der Nginx Config (Optional) # - ./config/docker/nginx/web-app/nginx.conf:/etc/nginx/nginx.conf:ro depends_on: api-gateway: condition: "service_started" networks: meldestelle-network: aliases: - "web-app" profiles: [ "gui", "all" ] # --- DESKTOP-APP (optional) --- desktop-app: build: context: . dockerfile: config/docker/nginx/desktop-app/Dockerfile args: BUILD_DATE: "${DOCKER_BUILD_DATE}" labels: - "org.opencontainers.image.created=${DOCKER_BUILD_DATE}" container_name: "${PROJECT_NAME:-meldestelle}-desktop-app" restart: "${RESTART_POLICY:-no}" environment: API_BASE_URL: "http://api-gateway:8081" ports: - "${DESKTOP_APP_VNC_PORT:-5901:5901}" - "${DESKTOP_APP_NOVNC_PORT:-6080:6080}" depends_on: api-gateway: condition: "service_started" networks: meldestelle-network: aliases: - "desktop-app" profiles: [ "gui", "all" ] volumes: postgres-data: pgadmin-data: redis-data: prometheus-data: grafana-data: networks: meldestelle-network: driver: bridge