services: postgres-primary: image: bitnami/postgresql-repmgr:16 container_name: soroban-registry-db-primary environment: POSTGRESQL_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD must be set} POSTGRESQL_USERNAME: ${POSTGRES_USER:-postgres} POSTGRESQL_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD must be set} POSTGRESQL_DATABASE: ${POSTGRES_DB:-soroban_registry} REPMGR_PASSWORD: ${REPMGR_PASSWORD:?REPMGR_PASSWORD must be set} REPMGR_PRIMARY_HOST: postgres-primary REPMGR_PRIMARY_PORT: "5432" REPMGR_PARTNER_NODES: postgres-primary,postgres-replica REPMGR_NODE_NAME: postgres-primary REPMGR_NODE_NETWORK_NAME: postgres-primary REPMGR_NODE_TYPE: primary REPMGR_PORT_NUMBER: "5432" REPMGR_USE_REPLICATION_SLOTS: "yes" ports: - "5433:5432" volumes: - postgres_primary_data:/bitnami/postgresql healthcheck: test: [ "CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-postgres} -d ${POSTGRES_DB:-soroban_registry}", ] interval: 10s timeout: 5s retries: 10 postgres-replica: image: bitnami/postgresql-repmgr:16 container_name: soroban-registry-db-replica environment: POSTGRESQL_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD must be set} POSTGRESQL_USERNAME: ${POSTGRES_USER:-postgres} POSTGRESQL_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD must be set} POSTGRESQL_DATABASE: ${POSTGRES_DB:-soroban_registry} REPMGR_PASSWORD: ${REPMGR_PASSWORD:?REPMGR_PASSWORD must be set} REPMGR_PRIMARY_HOST: postgres-primary REPMGR_PRIMARY_PORT: "5432" REPMGR_PARTNER_NODES: postgres-primary,postgres-replica REPMGR_NODE_NAME: postgres-replica REPMGR_NODE_NETWORK_NAME: postgres-replica REPMGR_NODE_TYPE: standby REPMGR_PORT_NUMBER: "5432" REPMGR_USE_REPLICATION_SLOTS: "yes" ports: - "5434:5432" volumes: - postgres_replica_data:/bitnami/postgresql depends_on: postgres-primary: condition: service_healthy healthcheck: test: [ "CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-postgres} -d ${POSTGRES_DB:-soroban_registry}", ] interval: 10s timeout: 5s retries: 10 pgpool: image: bitnami/pgpool:4 container_name: soroban-registry-pgpool environment: PGPOOL_BACKEND_NODES: 0:postgres-primary:5432,1:postgres-replica:5432 PGPOOL_ENABLE_LOAD_BALANCING: "yes" PGPOOL_POSTGRES_USERNAME: ${POSTGRES_USER:-postgres} PGPOOL_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD must be set} PGPOOL_SR_CHECK_USER: ${POSTGRES_USER:-postgres} PGPOOL_SR_CHECK_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD must be set} PGPOOL_ADMIN_USERNAME: pgpool_admin PGPOOL_ADMIN_PASSWORD: ${PGPOOL_ADMIN_PASSWORD:?PGPOOL_ADMIN_PASSWORD must be set} PGPOOL_NUM_INIT_CHILDREN: "32" PGPOOL_MAX_POOL: "4" PGPOOL_LOG_CONNECTIONS: "yes" PGPOOL_LOG_HOSTNAME: "yes" ports: - "5432:5432" depends_on: postgres-primary: condition: service_healthy postgres-replica: condition: service_healthy healthcheck: test: [ "CMD-SHELL", "pg_isready -h 127.0.0.1 -p 5432 -U ${POSTGRES_USER:-postgres} -d ${POSTGRES_DB:-soroban_registry}", ] interval: 10s timeout: 5s retries: 10 redis: image: redis:7-alpine container_name: soroban-registry-redis ports: - "6379:6379" volumes: - redis_data:/data command: redis-server --maxmemory 256mb --maxmemory-policy allkeys-lru healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 10s timeout: 5s retries: 5 api: build: context: . dockerfile: backend/Dockerfile container_name: soroban-registry-api environment: DATABASE_URL: postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:?err}@pgpool:5432/${POSTGRES_DB:-soroban_registry} DATABASE_PRIMARY_URL: postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:?err}@postgres-primary:5432/${POSTGRES_DB:-soroban_registry} DATABASE_REPLICA_URL: postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:?err}@postgres-replica:5432/${POSTGRES_DB:-soroban_registry} DATABASE_REPLICATION_LAG_THRESHOLD_MS: ${DATABASE_REPLICATION_LAG_THRESHOLD_MS:-100} RUST_LOG: ${RUST_LOG:-info} OTLP_ENDPOINT: http://jaeger:4318 OTEL_SERVICE_NAME: soroban-registry-api REDIS_ENABLED: ${REDIS_ENABLED:-true} REDIS_URL: redis://redis:6379 ports: - "3001:3001" depends_on: pgpool: condition: service_healthy redis: condition: service_healthy jaeger: condition: service_started healthcheck: test: ["CMD", "curl", "-sf", "http://localhost:3001/health"] interval: 15s timeout: 5s retries: 3 start_period: 30s volumes: - ./backend:/app - cargo_cache:/usr/local/cargo/registry indexer: build: context: . dockerfile: backend/Dockerfile container_name: soroban-registry-indexer command: ["/app/indexer"] environment: DATABASE_URL: postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:?err}@pgpool:5432/${POSTGRES_DB:-soroban_registry} RUST_LOG: ${INDEXER_RUST_LOG:-indexer=info} STELLAR_NETWORK: ${STELLAR_NETWORK:-testnet} OTLP_ENDPOINT: http://jaeger:4317 OTEL_SERVICE_NAME: soroban-registry-indexer depends_on: pgpool: condition: service_healthy jaeger: condition: service_started restart: unless-stopped frontend: build: context: ./frontend dockerfile: Dockerfile.dev container_name: soroban-registry-frontend environment: NEXT_PUBLIC_API_URL: http://localhost:3001 ports: - "3000:3000" depends_on: - api volumes: - ./frontend:/app - /app/node_modules - /app/.next tagging-service: build: context: ./tagging-service dockerfile: Dockerfile container_name: soroban-registry-tagging environment: DATABASE_URL: postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:?err}@pgpool:5432/${POSTGRES_DB:-soroban_registry} PORT: "3002" ports: - "3002:3002" depends_on: pgpool: condition: service_healthy jaeger: image: jaegertracing/all-in-one:1.55 container_name: soroban-jaeger environment: COLLECTOR_OTLP_ENABLED: "true" ports: - "16686:16686" - "4317:4317" - "4318:4318" healthcheck: test: ["CMD", "wget", "--spider", "-q", "http://localhost:14269/"] interval: 10s timeout: 5s retries: 3 prometheus: image: prom/prometheus:v2.49.1 container_name: soroban-prometheus 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 - --web.enable-lifecycle ports: - "9090:9090" volumes: - ./observability/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro - ./observability/prometheus/alert_rules.yml:/etc/prometheus/alert_rules.yml:ro - prometheus_data:/prometheus # Lets the host.docker.internal:9100 node-exporter scrape target resolve on # Linux as well as Docker Desktop. extra_hosts: - "host.docker.internal:host-gateway" depends_on: - api alertmanager: image: prom/alertmanager:v0.26.0 container_name: soroban-alertmanager # alertmanager.yml points slack_api_url_file/routing_key_file at # /alertmanager/secrets/*; materialize those from env vars before the # binary starts, since Alertmanager's config file has no native env # expansion. Empty values are fine locally — Alertmanager only reads the # file contents when it actually sends a notification. entrypoint: ["/bin/sh", "-c"] command: - | mkdir -p /alertmanager/secrets printf '%s' "$$SLACK_WEBHOOK_URL" > /alertmanager/secrets/slack_webhook_url printf '%s' "$$PAGERDUTY_SERVICE_KEY" > /alertmanager/secrets/pagerduty_routing_key exec /bin/alertmanager --config.file=/etc/alertmanager/alertmanager.yml --storage.path=/alertmanager environment: SLACK_WEBHOOK_URL: ${SLACK_WEBHOOK_URL:-} PAGERDUTY_SERVICE_KEY: ${PAGERDUTY_SERVICE_KEY:-} ports: - "9093:9093" volumes: - ./observability/alertmanager/alertmanager.yml:/etc/alertmanager/alertmanager.yml:ro - alertmanager_data:/alertmanager loki: image: grafana/loki:2.9.4 container_name: soroban-loki command: -config.file=/etc/loki/loki-config.yaml ports: - "3100:3100" volumes: - ./observability/loki/loki-config.yaml:/etc/loki/loki-config.yaml:ro - loki_data:/loki promtail: image: grafana/promtail:2.9.4 container_name: soroban-promtail command: -config.file=/etc/promtail/promtail-config.yaml volumes: - ./observability/promtail/promtail-config.yaml:/etc/promtail/promtail-config.yaml:ro - /var/run/docker.sock:/var/run/docker.sock depends_on: - loki grafana: image: grafana/grafana:10.2.3 container_name: soroban-grafana environment: GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_ADMIN_PASSWORD:?GRAFANA_ADMIN_PASSWORD must be set} GF_USERS_ALLOW_SIGN_UP: "false" GF_FEATURE_TOGGLES_ENABLE: tempoSearch ports: - "3003:3000" volumes: - ./observability/grafana/provisioning:/etc/grafana/provisioning:ro - ./observability/grafana/dashboards:/etc/grafana/dashboards:ro - grafana_data:/var/lib/grafana depends_on: - prometheus - loki - jaeger node-exporter: image: prom/node-exporter:v1.7.0 container_name: soroban-node-exporter command: - --path.rootfs=/host network_mode: host pid: host volumes: - /:/host:ro,rslave elasticsearch: image: elasticsearch:8.12.0 container_name: soroban-elasticsearch environment: - discovery.type=single-node - xpack.security.enabled=false - "ES_JAVA_OPTS=-Xms512m -Xmx512m" ulimits: memlock: soft: -1 hard: -1 ports: - "9200:9200" volumes: - es_data:/usr/share/elasticsearch/data volumes: postgres_primary_data: postgres_replica_data: cargo_cache: prometheus_data: alertmanager_data: loki_data: grafana_data: es_data: