services: postgres: image: postgres:18.6 restart: unless-stopped command: - postgres - -c - shared_buffers=64MB - -c - max_connections=20 - -c - work_mem=2MB environment: POSTGRES_DB: ${POSTGRES_DB:?Set POSTGRES_DB} POSTGRES_USER: ${POSTGRES_USER:?Set POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD} volumes: - postgres_data:/var/lib/postgresql healthcheck: test: [ "CMD-SHELL", "pg_isready -U \"$${POSTGRES_USER}\" -d \"$${POSTGRES_DB}\"" ] interval: 10s timeout: 5s retries: 5 start_period: 10s redis: image: redis:8.0 restart: unless-stopped command: [ "redis-server", "--requirepass", "${REDIS_PASSWORD:?Set REDIS_PASSWORD}", "--appendonly", "yes" ] environment: REDISCLI_AUTH: ${REDIS_PASSWORD:?Set REDIS_PASSWORD} volumes: - redis_data:/data healthcheck: test: ["CMD-SHELL", "redis-cli ping | grep -q PONG"] interval: 10s timeout: 5s retries: 5 start_period: 10s kafka: image: apache/kafka:4.0.2 restart: unless-stopped environment: KAFKA_NODE_ID: 1 KAFKA_PROCESS_ROLES: broker,controller KAFKA_LISTENERS: PLAINTEXT://:9092,CONTROLLER://:9093 KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092 KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka:9093 KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1 KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1 KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: 0 KAFKA_NUM_PARTITIONS: 3 KAFKA_LOG_DIRS: /var/lib/kafka/data KAFKA_HEAP_OPTS: "-Xms128m -Xmx256m" volumes: - kafka_data:/var/lib/kafka/data healthcheck: test: [ "CMD-SHELL", "/opt/kafka/bin/kafka-topics.sh --bootstrap-server localhost:9092 --list >/dev/null 2>&1" ] interval: 10s timeout: 5s retries: 12 start_period: 20s app: image: ${APP_IMAGE:-ai-fitness-coach-backend:local} restart: unless-stopped init: true depends_on: postgres: condition: service_healthy redis: condition: service_healthy kafka: condition: service_healthy environment: SPRING_PROFILES_ACTIVE: prod JAVA_TOOL_OPTIONS: "-Xms64m -Xmx256m -XX:MaxMetaspaceSize=160m -XX:ReservedCodeCacheSize=48m -XX:MaxDirectMemorySize=64m -XX:+UseSerialGC" SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/${POSTGRES_DB:?Set POSTGRES_DB} SPRING_DATASOURCE_USERNAME: ${POSTGRES_USER:?Set POSTGRES_USER} SPRING_DATASOURCE_PASSWORD: ${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD} SPRING_DATA_REDIS_HOST: redis SPRING_DATA_REDIS_PORT: "6379" SPRING_DATA_REDIS_PASSWORD: ${REDIS_PASSWORD:?Set REDIS_PASSWORD} SPRING_KAFKA_BOOTSTRAP_SERVERS: kafka:9092 JWT_SECRET: ${JWT_SECRET:?Set JWT_SECRET} JWT_EXPIRATION_SECONDS: ${JWT_EXPIRATION_SECONDS:-3600} GEMINI_API_KEY: ${GEMINI_API_KEY:?Set GEMINI_API_KEY} TELEGRAM_BOT_TOKEN: ${TELEGRAM_BOT_TOKEN:?Set TELEGRAM_BOT_TOKEN} INTERVALS_ICU_BASE_URL: ${INTERVALS_ICU_BASE_URL:-https://intervals.icu/api/v1} INTERVALS_API_KEY: ${INTERVALS_API_KEY:-} INTERVALS_OWNER_USER_ID: ${INTERVALS_OWNER_USER_ID:-0} INTERVALS_OAUTH_CLIENT_ID: ${INTERVALS_OAUTH_CLIENT_ID:-} INTERVALS_OAUTH_CLIENT_SECRET: ${INTERVALS_OAUTH_CLIENT_SECRET:-} INTERVALS_OAUTH_REDIRECT_URI: ${INTERVALS_OAUTH_REDIRECT_URI:-} INTERVALS_TOKEN_ENCRYPTION_KEY: ${INTERVALS_TOKEN_ENCRYPTION_KEY:-} # Only the server itself can access this port. # HTTPS reverse proxy will be configured in a later step. ports: - "127.0.0.1:8080:8080" volumes: postgres_data: redis_data: kafka_data: