name: han-processing x-hardening: &hardening read_only: true security_opt: - no-new-privileges:true cap_drop: - ALL restart: unless-stopped x-postgres-ca-volume: &postgres-ca-volume type: bind source: ${PG_CA_HOST_PATH:?set PostgreSQL CA host path} target: /run/config/postgresql-ca.pem read_only: true x-message-safety-environment: &message-safety-environment APP_ENV: ${APP_ENV:?set APP_ENV} MESSAGE_SAFETY_HOST: ${MESSAGE_SAFETY_HOST:-0.0.0.0} MESSAGE_SAFETY_PORT: ${MESSAGE_SAFETY_PORT:-8080} MESSAGE_SAFETY_WORKER_CONCURRENCY: ${MESSAGE_SAFETY_WORKER_CONCURRENCY:-5} MESSAGE_SAFETY_DNS_RESOLVERS: ${MESSAGE_SAFETY_DNS_RESOLVERS:?set trusted DNS resolvers} MESSAGE_SAFETY_CLAMAV_HOST: ${MESSAGE_SAFETY_CLAMAV_HOST:-clamd} MESSAGE_SAFETY_CLAMAV_PORT: ${MESSAGE_SAFETY_CLAMAV_PORT:-3310} MESSAGE_SAFETY_ARTIFACTS_DIR: ${MESSAGE_SAFETY_ARTIFACTS_DIR:-/app/app/artifacts} MESSAGE_SAFETY_MODE_FILE: /etc/han-chat/message-safety-mode.env PG_CA_FILE: /run/config/postgresql-ca.pem SELECTEL_S3_ENDPOINT_URL: ${SELECTEL_S3_ENDPOINT_URL:?set S3 endpoint} SELECTEL_S3_BUCKET_QUARANTINE: ${SELECTEL_S3_BUCKET_QUARANTINE:?set quarantine bucket} OTEL_EXPORTER_OTLP_ENDPOINT: ${OTEL_EXPORTER_OTLP_ENDPOINT:-http://otel-collector:4317} MESSAGE_SAFETY_DATABASE_URL_FILE: /run/secrets/message_safety_database_url MESSAGE_SAFETY_REDIS_URL_FILE: /run/secrets/message_safety_redis_url MESSAGE_SAFETY_SERVICE_TOKEN_FILE: /run/secrets/message_safety_service_token SELECTEL_S3_QUARANTINE_READ_ACCESS_KEY_FILE: /run/secrets/s3_quarantine_read_access_key SELECTEL_S3_QUARANTINE_READ_SECRET_KEY_FILE: /run/secrets/s3_quarantine_read_secret_key x-bitrix-sync-environment: &bitrix-sync-environment APP_ENV: ${APP_ENV:?set APP_ENV} BITRIX_SYNC_ENABLED: ${BITRIX_SYNC_ENABLED:-false} BITRIX_SYNC_MODE: ${BITRIX_SYNC_MODE:-disabled} BITRIX_SYNC_PORTAL_HOST: ${BITRIX_SYNC_PORTAL_HOST:?set approved portal} BITRIX_SYNC_PORTAL_MEMBER_ID: ${BITRIX_SYNC_PORTAL_MEMBER_ID:?set member id} BITRIX_SYNC_PUBLIC_BASE_URL: ${BITRIX_SYNC_PUBLIC_BASE_URL:?set public base URL} BITRIX_SYNC_CONTACT_USER_ID_FIELD: ${BITRIX_SYNC_CONTACT_USER_ID_FIELD:?set contact field} BITRIX_SYNC_CONTACT_REGISTERED_FIELD: ${BITRIX_SYNC_CONTACT_REGISTERED_FIELD:?set registration field} BITRIX_SYNC_CONTACT_CITIZENSHIP_FIELD: ${BITRIX_SYNC_CONTACT_CITIZENSHIP_FIELD:?set citizenship field} BITRIX_SYNC_WEBHOOK_ALLOWED_CIDRS: ${BITRIX_WEBHOOK_ALLOWED_CIDRS:-} BITRIX_SYNC_HTTP_TIMEOUT_SEC: ${BITRIX_SYNC_HTTP_TIMEOUT_SEC:-10} BITRIX_SYNC_DB_POOL_SIZE: ${BITRIX_SYNC_DB_POOL_SIZE:-5} PG_CA_FILE: /run/config/postgresql-ca.pem OTEL_EXPORTER_OTLP_ENDPOINT: ${OTEL_EXPORTER_OTLP_ENDPOINT:-http://otel-collector:4317} BITRIX_SYNC_DATABASE_URL_FILE: /run/secrets/bitrix_sync_database_url BITRIX_SYNC_CRM_REST_WEBHOOK_URL_FILE: /run/secrets/bitrix_sync_crm_rest_webhook_url BITRIX_SYNC_CONTACT_RECEIVER_TOKEN_FILE: /run/secrets/bitrix_sync_contact_receiver_token BITRIX_SYNC_ALERT_RECEIVER_TOKEN_FILE: /run/secrets/bitrix_sync_alert_receiver_token BITRIX_SYNC_SERVICE_TOKEN_FILE: /run/secrets/bitrix_sync_service_token services: nginx: <<: *hardening image: ${NGINX_IMAGE:?set immutable nginx image digest} user: "101:11001" environment: PROCESSING_PUBLIC_HOST: ${PROCESSING_PUBLIC_HOST:?set PROCESSING_PUBLIC_HOST} MESSAGE_SAFETY_UPSTREAM_HOST: message-safety-api BITRIX_SYNC_UPSTREAM_HOST: bitrix-sync ports: - "80:8080" - "443:8444" - "${PROCESSING_PRIVATE_BIND_ADDRESS:?set private bind IP}:8443:8443" volumes: - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro - ./nginx/templates:/etc/nginx/templates:ro - ./nginx/allowlists:/etc/nginx/allowlists:ro - /var/lib/han-chat/public-tls:/run/public-tls:ro - /var/lib/han-chat/acme:/var/www/certbot:ro secrets: - source: internal_tls_certificate target: internal_tls_certificate mode: 0444 - source: internal_tls_private_key target: internal_tls_private_key mode: 0444 tmpfs: - /tmp:rw,noexec,nosuid,nodev,size=64m - /var/cache/nginx:rw,noexec,nosuid,nodev,size=64m,uid=101,gid=11001,mode=0750 - /var/run:rw,noexec,nosuid,nodev,size=8m,uid=101,gid=11001,mode=0750 - /etc/nginx/conf.d:rw,noexec,nosuid,nodev,size=8m,uid=101,gid=11001,mode=0750 ulimits: nofile: soft: 4096 hard: 4096 networks: [public, backend] depends_on: message-safety-api: condition: service_started bitrix-sync: condition: service_started healthcheck: test: ["CMD", "nginx", "-t", "-q", "-c", "/etc/nginx/nginx.conf"] interval: 15s timeout: 3s retries: 3 start_period: 10s pids_limit: 200 mem_limit: 256m cpus: 1.0 redis-safety: <<: *hardening image: ${REDIS_IMAGE:?set immutable Redis image digest} user: "999:999" command: ["redis-server", "/usr/local/etc/redis/redis.conf"] volumes: - ./redis/redis.conf:/usr/local/etc/redis/redis.conf:ro - redis-safety-data:/data secrets: - source: redis_safety_acl target: redis-safety.acl mode: 0444 networks: [backend] expose: ["6379"] healthcheck: test: ["CMD", "redis-cli", "--no-auth-warning", "PING"] interval: 15s timeout: 3s retries: 3 pids_limit: 100 mem_limit: 640m cpus: 1.0 clamd: <<: *hardening image: ${CLAMAV_IMAGE:?set immutable ClamAV image digest} entrypoint: ["/init-unprivileged"] user: "100:101" command: ["clamd", "--foreground=true"] volumes: - clamav-signatures:/var/lib/clamav:ro - clamav-runtime:/run/clamav tmpfs: - /tmp:rw,noexec,nosuid,nodev,size=64m - /var/log/clamav:rw,noexec,nosuid,nodev,size=32m,uid=100,gid=101,mode=0750 networks: [backend] expose: ["3310"] healthcheck: test: ["CMD-SHELL", "clamdscan --ping 1 >/dev/null 2>&1"] interval: 30s timeout: 5s retries: 5 start_period: 60s pids_limit: 300 mem_limit: 2g cpus: 2.0 freshclam: <<: *hardening image: ${CLAMAV_IMAGE:?set immutable ClamAV image digest} entrypoint: ["/init-unprivileged"] user: "100:101" command: ["freshclam", "--daemon", "--foreground", "--checks=12"] volumes: - clamav-signatures:/var/lib/clamav - clamav-runtime:/run/clamav tmpfs: - /tmp:rw,noexec,nosuid,nodev,size=64m - /var/log/clamav:rw,noexec,nosuid,nodev,size=32m,uid=100,gid=101,mode=0750 healthcheck: disable: true networks: [signature-egress] pids_limit: 100 mem_limit: 256m cpus: 0.5 otel-queue-init: <<: *hardening image: ${REDIS_IMAGE:?set immutable Redis image digest} entrypoint: ["sh", "-c"] command: ["chown 10001:10001 /queue && chmod 0700 /queue"] user: "0:0" restart: "no" network_mode: none volumes: - otel-queue:/queue cap_add: - CHOWN - FOWNER pids_limit: 20 mem_limit: 32m cpus: 0.1 otel-collector: <<: *hardening image: ${OTEL_COLLECTOR_IMAGE:?set immutable Collector image digest} user: "10001:10001" command: ["--config=/etc/otelcol-contrib/config.yaml"] environment: APP_ENV: ${APP_ENV:?set APP_ENV} RELEASE_VERSION: ${RELEASE_VERSION:?set RELEASE_VERSION} OTEL_REMOTE_ENDPOINT: ${OTEL_REMOTE_ENDPOINT:?set private OTLP endpoint} OTEL_REMOTE_TLS_INSECURE: ${OTEL_REMOTE_TLS_INSECURE:?set OTLP TLS mode} volumes: - ./observability/otel-collector.yaml:/etc/otelcol-contrib/config.yaml:ro - otel-queue:/var/lib/otelcol/queue networks: observability: {} telemetry-egress: gw_priority: 1 depends_on: otel-queue-init: condition: service_completed_successfully expose: ["4317", "4318"] healthcheck: test: ["CMD", "/otelcol-contrib", "components"] interval: 30s timeout: 5s retries: 3 pids_limit: 200 mem_limit: 512m cpus: 1.0 message-safety-api: <<: *hardening image: ${MESSAGE_SAFETY_IMAGE:?set immutable message-safety image digest} command: ["message-safety"] user: "10001:10001" environment: <<: *message-safety-environment MESSAGE_SAFETY_PROCESS_ROLE: api volumes: - /etc/han-chat/message-safety-mode.env:/etc/han-chat/message-safety-mode.env:ro - *postgres-ca-volume tmpfs: - /tmp:rw,noexec,nosuid,nodev,size=64m networks: backend: {} observability: {} safety-egress: gw_priority: 1 expose: ["8080"] secrets: - message_safety_database_url - message_safety_redis_url - message_safety_service_token depends_on: redis-safety: condition: service_healthy healthcheck: test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8080/health/live',timeout=2)"] interval: 15s timeout: 3s retries: 3 start_period: 20s pids_limit: 200 mem_limit: 512m cpus: 1.0 message-safety-worker: <<: *hardening image: ${MESSAGE_SAFETY_IMAGE:?set immutable message-safety image digest} command: ["message-safety-worker"] user: "10001:10001" environment: <<: *message-safety-environment MESSAGE_SAFETY_PROCESS_ROLE: worker volumes: - /etc/han-chat/message-safety-mode.env:/etc/han-chat/message-safety-mode.env:ro - *postgres-ca-volume tmpfs: - /tmp:rw,noexec,nosuid,nodev,size=256m networks: backend: {} observability: {} safety-egress: gw_priority: 1 secrets: - message_safety_database_url - message_safety_redis_url - s3_quarantine_read_access_key - s3_quarantine_read_secret_key depends_on: redis-safety: condition: service_healthy clamd: condition: service_healthy pids_limit: 400 mem_limit: 2g cpus: 2.0 message-safety-migrate: <<: *hardening image: ${MESSAGE_SAFETY_IMAGE:?set immutable message-safety image digest} entrypoint: ["alembic"] command: ["upgrade", "head"] user: "10001:10001" restart: "no" profiles: ["ops"] environment: MESSAGE_SAFETY_CONFIG_ADMIN_DATABASE_URL_FILE: /run/secrets/message_safety_config_admin_database_url PG_CA_FILE: /run/config/postgresql-ca.pem volumes: - *postgres-ca-volume networks: safety-egress: gw_priority: 1 secrets: - message_safety_config_admin_database_url tmpfs: - /tmp:rw,noexec,nosuid,nodev,size=32m pids_limit: 100 mem_limit: 256m cpus: 0.5 bitrix-sync: <<: *hardening image: ${BITRIX_SYNC_IMAGE:?set immutable bitrix-sync image digest} command: ["han-bitrix-sync-api"] user: "10001:10001" environment: *bitrix-sync-environment volumes: - *postgres-ca-volume networks: backend: {} observability: {} bitrix-egress: gw_priority: 1 expose: ["8080"] secrets: - bitrix_sync_database_url - bitrix_sync_crm_rest_webhook_url - bitrix_sync_contact_receiver_token - bitrix_sync_alert_receiver_token - bitrix_sync_service_token tmpfs: - /tmp:rw,noexec,nosuid,nodev,size=32m healthcheck: test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8080/health/live',timeout=2)"] interval: 15s timeout: 3s retries: 3 start_period: 20s pids_limit: 200 mem_limit: 384m cpus: 1.0 bitrix-sync-worker: <<: *hardening image: ${BITRIX_SYNC_IMAGE:?set immutable bitrix-sync image digest} command: ["han-bitrix-sync-worker"] user: "10001:10001" environment: *bitrix-sync-environment volumes: - *postgres-ca-volume networks: observability: {} bitrix-egress: gw_priority: 1 secrets: - bitrix_sync_database_url - bitrix_sync_crm_rest_webhook_url - bitrix_sync_contact_receiver_token - bitrix_sync_alert_receiver_token - bitrix_sync_service_token tmpfs: - /tmp:rw,noexec,nosuid,nodev,size=32m pids_limit: 200 mem_limit: 512m cpus: 1.0 bitrix-sync-reconciliation: <<: *hardening image: ${BITRIX_SYNC_IMAGE:?set immutable bitrix-sync image digest} command: ["han-bitrix-sync-reconciliation"] user: "10001:10001" environment: *bitrix-sync-environment volumes: - *postgres-ca-volume networks: observability: {} bitrix-egress: gw_priority: 1 secrets: - bitrix_sync_database_url - bitrix_sync_crm_rest_webhook_url - bitrix_sync_contact_receiver_token - bitrix_sync_alert_receiver_token - bitrix_sync_service_token tmpfs: - /tmp:rw,noexec,nosuid,nodev,size=32m pids_limit: 150 mem_limit: 384m cpus: 0.75 bitrix-sync-migrate: <<: *hardening image: ${BITRIX_SYNC_IMAGE:?set immutable bitrix-sync image digest} entrypoint: ["alembic"] command: ["upgrade", "head"] user: "10001:10001" restart: "no" profiles: ["ops"] environment: BITRIX_SYNC_MIGRATION_DATABASE_URL_FILE: /run/secrets/bitrix_sync_migration_database_url PG_CA_FILE: /run/config/postgresql-ca.pem volumes: - *postgres-ca-volume networks: bitrix-egress: gw_priority: 1 secrets: - bitrix_sync_migration_database_url tmpfs: - /tmp:rw,noexec,nosuid,nodev,size=32m pids_limit: 100 mem_limit: 256m cpus: 0.5 networks: public: backend: internal: true observability: internal: true safety-egress: bitrix-egress: signature-egress: telemetry-egress: volumes: redis-safety-data: clamav-signatures: clamav-runtime: otel-queue: secrets: message_safety_database_url: file: /run/han-chat/secrets/MESSAGE_SAFETY_DATABASE_URL message_safety_config_admin_database_url: file: /run/han-chat/secrets/MESSAGE_SAFETY_CONFIG_ADMIN_DATABASE_URL message_safety_redis_url: file: /run/han-chat/secrets/MESSAGE_SAFETY_REDIS_URL message_safety_service_token: file: /run/han-chat/secrets/MESSAGE_SAFETY_SERVICE_TOKEN s3_quarantine_read_access_key: file: /run/han-chat/secrets/SELECTEL_S3_QUARANTINE_READ_ACCESS_KEY s3_quarantine_read_secret_key: file: /run/han-chat/secrets/SELECTEL_S3_QUARANTINE_READ_SECRET_KEY internal_tls_certificate: file: /run/han-chat/secrets/VM2_INTERNAL_TLS_CERTIFICATE internal_tls_private_key: file: /run/han-chat/secrets/VM2_INTERNAL_TLS_PRIVATE_KEY bitrix_sync_database_url: file: /run/han-chat/secrets/BITRIX_SYNC_DATABASE_URL bitrix_sync_migration_database_url: file: /run/han-chat/secrets/BITRIX_SYNC_MIGRATION_DATABASE_URL bitrix_sync_crm_rest_webhook_url: file: /run/han-chat/secrets/BITRIX_SYNC_CRM_REST_WEBHOOK_URL bitrix_sync_contact_receiver_token: file: /run/han-chat/secrets/BITRIX_SYNC_CONTACT_RECEIVER_TOKEN bitrix_sync_alert_receiver_token: file: /run/han-chat/secrets/BITRIX_SYNC_ALERT_RECEIVER_TOKEN bitrix_sync_service_token: file: /run/han-chat/secrets/BITRIX_SYNC_SERVICE_TOKEN redis_safety_acl: file: /run/han-chat/secrets/REDIS_SAFETY_ACL