x-no-sms-secrets: &no-sms-secrets SMS_DATABASE_URL: "" SMS_SERVICE_TOKEN: "" KEYCLOAK_SMS_SERVICE_TOKEN: "" IDGTL_SMS_API_KEY: "" IDGTL_SMS_CALLBACK_USERNAME: "" IDGTL_SMS_CALLBACK_PASSWORD: "" KEYCLOAK_YANDEX_CAPTCHA_ENABLED: "" KEYCLOAK_YANDEX_CAPTCHA_CLIENT_KEY: "" KEYCLOAK_YANDEX_CAPTCHA_SERVER_KEY: "" x-api-runtime: &api-runtime build: context: ../../api-backend image: ${API_BACKEND_IMAGE:-han-chat-api-backend:local} env_file: - path: ../../.env required: false environment: *no-sms-secrets volumes: - type: bind source: ${PG_CA_HOST_PATH} target: /run/secrets/pg-ca.pem read_only: true networks: [backend, observability, egress] security_opt: ["no-new-privileges:true"] logging: driver: json-file options: {max-size: "50m", max-file: "5"} x-sms-runtime: &sms-runtime build: context: ../../sms-service image: ${SMS_SERVICE_IMAGE:-han-chat-sms-service:local} environment: SMS_DATABASE_URL: ${SMS_DATABASE_URL} SMS_SERVICE_TOKEN: ${SMS_SERVICE_TOKEN} IDGTL_SMS_BASE_URL: ${IDGTL_SMS_BASE_URL:-https://direct.i-dgtl.ru} IDGTL_SMS_CALLBACK_PUBLIC_URL: ${IDGTL_SMS_CALLBACK_PUBLIC_URL} IDGTL_SMS_CALLBACK_USERNAME: ${IDGTL_SMS_CALLBACK_USERNAME} IDGTL_SMS_CALLBACK_PASSWORD: ${IDGTL_SMS_CALLBACK_PASSWORD} LOG_LEVEL: ${LOG_LEVEL:-INFO} OTEL_EXPORTER_OTLP_ENDPOINT: ${OTEL_EXPORTER_OTLP_ENDPOINT:-http://otel-collector:4317} volumes: - type: bind source: ${PG_CA_HOST_PATH} target: /run/secrets/pg-ca.pem read_only: true security_opt: ["no-new-privileges:true"] logging: driver: json-file options: {max-size: "50m", max-file: "5"} services: frontend-static: build: context: ../../frontend-test-site target: static args: EXPO_PUBLIC_API_BASE_URL: ${PUBLIC_WEB_URL} EXPO_PUBLIC_AUTH_BASE_URL: ${PUBLIC_AUTH_URL} EXPO_PUBLIC_KEYCLOAK_REALM: ${KEYCLOAK_REALM:-han-chat} EXPO_PUBLIC_KEYCLOAK_CLIENT_ID: han-chat-frontend EXPO_PUBLIC_APP_ENV: ${APP_ENV:-production-like} image: han-chat-frontend-static:${RELEASE_VERSION:-local} volumes: - frontend-static:/output restart: "no" read_only: true tmpfs: - /tmp:size=8m,mode=1777 cap_drop: ["ALL"] security_opt: ["no-new-privileges:true"] keycloak: build: context: ../../keycloak image: ${KEYCLOAK_IMAGE:-han-chat-keycloak:local} env_file: - path: ../../.env required: false environment: <<: *no-sms-secrets KC_DB: postgres KC_DB_URL: ${KEYCLOAK_DB_URL} KC_DB_SCHEMA: ${KEYCLOAK_DB_SCHEMA:-keycloak} KC_DB_USERNAME: ${KEYCLOAK_DB_USERNAME} KC_DB_PASSWORD: ${KEYCLOAK_DB_PASSWORD} KC_PROXY_HEADERS: xforwarded KC_HTTP_ENABLED: "true" KC_HTTP_RELATIVE_PATH: /auth KC_HEALTH_ENABLED: "true" KC_METRICS_ENABLED: "true" KC_HOSTNAME: ${KEYCLOAK_PUBLIC_URL} KC_BOOTSTRAP_ADMIN_USERNAME: ${KEYCLOAK_ADMIN} KC_BOOTSTRAP_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD} KEYCLOAK_OTP_MOCK_ENABLED: ${KEYCLOAK_OTP_MOCK_ENABLED:-false} KEYCLOAK_OTP_MOCK_CODE: ${KEYCLOAK_OTP_MOCK_CODE:-} KEYCLOAK_YANDEX_CAPTCHA_ENABLED: ${KEYCLOAK_YANDEX_CAPTCHA_ENABLED:-false} KEYCLOAK_YANDEX_CAPTCHA_CLIENT_KEY: ${KEYCLOAK_YANDEX_CAPTCHA_CLIENT_KEY:-} KEYCLOAK_YANDEX_CAPTCHA_SERVER_KEY: ${KEYCLOAK_YANDEX_CAPTCHA_SERVER_KEY:-} KEYCLOAK_OTP_HMAC_KEY: ${KEYCLOAK_OTP_HMAC_KEY:?KEYCLOAK_OTP_HMAC_KEY is required} KEYCLOAK_OTP_SETTINGS_MAX_STALE_SEC: ${KEYCLOAK_OTP_SETTINGS_MAX_STALE_SEC:-300} KEYCLOAK_SETTINGS_BRIDGE_URL: ${KEYCLOAK_SETTINGS_BRIDGE_URL:-http://api-backend:8000/internal/settings/v1/otp} KEYCLOAK_SETTINGS_BRIDGE_TOKEN: ${KEYCLOAK_SETTINGS_BRIDGE_TOKEN:?KEYCLOAK_SETTINGS_BRIDGE_TOKEN is required} KEYCLOAK_SMS_SERVICE_URL: ${KEYCLOAK_SMS_SERVICE_URL:-http://sms-service:8080} KEYCLOAK_SMS_SERVICE_TOKEN: ${KEYCLOAK_SMS_SERVICE_TOKEN:?KEYCLOAK_SMS_SERVICE_TOKEN is required} command: ["start", "--optimized", "--import-realm"] expose: ["8080", "9000"] volumes: - type: bind source: ${PG_CA_HOST_PATH} target: /run/secrets/pg-ca.pem read_only: true networks: [public, backend, observability, egress] healthcheck: test: ["CMD-SHELL", "exec 3<>/dev/tcp/127.0.0.1/9000 && printf 'GET /auth/health/ready HTTP/1.0\r\n\r\n' >&3 && grep -q '200 OK' <&3"] interval: 15s timeout: 5s retries: 12 start_period: 60s restart: unless-stopped security_opt: ["no-new-privileges:true"] logging: driver: json-file options: {max-size: "50m", max-file: "5"} sms-service: <<: *sms-runtime expose: ["8080"] networks: [backend, observability, egress] healthcheck: test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8080/health/ready', timeout=3)"] interval: 10s timeout: 5s retries: 12 start_period: 30s restart: unless-stopped sms-worker: <<: *sms-runtime entrypoint: [] command: ["han-sms-worker"] environment: SMS_DATABASE_URL: ${SMS_DATABASE_URL} SMS_SERVICE_TOKEN: ${SMS_SERVICE_TOKEN} IDGTL_SMS_BASE_URL: ${IDGTL_SMS_BASE_URL:-https://direct.i-dgtl.ru} IDGTL_SMS_API_KEY: ${IDGTL_SMS_API_KEY:?IDGTL_SMS_API_KEY is required} IDGTL_SMS_CALLBACK_PUBLIC_URL: ${IDGTL_SMS_CALLBACK_PUBLIC_URL} IDGTL_SMS_CALLBACK_USERNAME: ${IDGTL_SMS_CALLBACK_USERNAME} IDGTL_SMS_CALLBACK_PASSWORD: ${IDGTL_SMS_CALLBACK_PASSWORD} LOG_LEVEL: ${LOG_LEVEL:-INFO} OTEL_EXPORTER_OTLP_ENDPOINT: ${OTEL_EXPORTER_OTLP_ENDPOINT:-http://otel-collector:4317} networks: [backend, observability, egress] depends_on: sms-service: {condition: service_healthy} healthcheck: test: ["CMD", "python", "-c", "from pathlib import Path; assert b'han-sms-worker' in Path('/proc/1/cmdline').read_bytes()"] interval: 30s timeout: 5s retries: 3 start_period: 10s restart: unless-stopped message-safety: build: context: ../../message-safety image: ${MESSAGE_SAFETY_IMAGE:-han-chat-message-safety:local} env_file: - path: ../../.env required: false environment: <<: *no-sms-secrets expose: ["8080"] volumes: - type: bind source: ${PG_CA_HOST_PATH} target: /run/secrets/pg-ca.pem read_only: true networks: [backend, observability, egress] depends_on: redis: {condition: service_healthy} healthcheck: test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8080/health/ready', timeout=3)"] interval: 10s timeout: 5s retries: 12 start_period: 30s restart: unless-stopped security_opt: ["no-new-privileges:true"] logging: driver: json-file options: {max-size: "50m", max-file: "5"} api-backend: <<: *api-runtime expose: ["8000"] depends_on: redis: {condition: service_healthy} keycloak: {condition: service_healthy} message-safety: {condition: service_healthy} healthcheck: test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health/ready', timeout=3)"] interval: 10s timeout: 5s retries: 12 start_period: 30s restart: unless-stopped delivery-worker: <<: *api-runtime entrypoint: [] command: ["han-delivery-worker"] depends_on: api-backend: {condition: service_healthy} bitrix-local-app: {condition: service_healthy} healthcheck: test: ["CMD", "python", "-c", "from pathlib import Path; assert b'han-delivery-worker' in Path('/proc/1/cmdline').read_bytes()"] interval: 30s timeout: 5s retries: 3 start_period: 10s restart: unless-stopped safety-recovery-worker: <<: *api-runtime entrypoint: [] command: ["han-safety-worker"] depends_on: api-backend: {condition: service_healthy} message-safety: {condition: service_healthy} healthcheck: test: ["CMD", "python", "-c", "from pathlib import Path; assert b'han-safety-worker' in Path('/proc/1/cmdline').read_bytes()"] interval: 30s timeout: 5s retries: 3 start_period: 10s restart: unless-stopped cleanup-worker: <<: *api-runtime entrypoint: [] command: ["han-cleanup-worker"] depends_on: api-backend: {condition: service_healthy} healthcheck: test: ["CMD", "python", "-c", "from pathlib import Path; assert b'han-cleanup-worker' in Path('/proc/1/cmdline').read_bytes()"] interval: 30s timeout: 5s retries: 3 start_period: 10s restart: unless-stopped bitrix-local-app: build: context: ../../bitrix-local-app image: ${BITRIX_LOCAL_APP_IMAGE:-han-chat-bitrix-local-app:local} env_file: - path: ../../.env required: false environment: <<: *no-sms-secrets expose: ["8080"] volumes: - type: bind source: ${PG_CA_HOST_PATH} target: /run/secrets/pg-ca.pem read_only: true networks: [backend, observability, egress] depends_on: api-backend: {condition: service_healthy} healthcheck: test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8080/health/live', timeout=3)"] interval: 10s timeout: 5s retries: 12 start_period: 30s restart: unless-stopped security_opt: ["no-new-privileges:true"] logging: driver: json-file options: {max-size: "50m", max-file: "5"} bitrix-sync: build: context: ../../bitrix-sync image: ${BITRIX_SYNC_IMAGE:-han-chat-bitrix-sync:local} env_file: - path: ../../.env required: false environment: <<: *no-sms-secrets expose: ["8080"] volumes: - type: bind source: ${PG_CA_HOST_PATH} target: /run/secrets/pg-ca.pem read_only: true networks: [backend, observability, egress] depends_on: redis: {condition: service_healthy} healthcheck: test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8080/health/live', timeout=3)"] interval: 15s timeout: 5s retries: 12 start_period: 30s restart: unless-stopped security_opt: ["no-new-privileges:true"] logging: driver: json-file options: {max-size: "50m", max-file: "5"}