version: '3.8' x-json-logging: &json-logging logging: driver: json-file options: max-size: "100m" max-file: "3" services: # Soroban local network (stellar quickstart) soroban: image: stellar/quickstart:latest command: --local --enable-soroban-rpc ports: - "8000:8000" healthcheck: # CMD-SHELL so a failure echoes a greppable WARN line into the health # log (docker inspect --format '{{json .State.Health}}'). test: ["CMD-SHELL", "curl -fsS http://localhost:8000 || (echo 'WARN: soroban health check failed' >&2; exit 1)"] interval: 5s timeout: 3s retries: 30 # Stellar quickstart takes a while to bring up Horizon + RPC. start_period: 30s <<: *json-logging # MPC Node 0 mpc-node-0: build: context: . dockerfile: services/node/Dockerfile environment: NODE_ID: "0" PORT: "8101" MPC_PORT: "10000" PARTY_CONFIG: "/app/config/party_0.toml" CIRCUIT_DIR: "/app/circuits" CRS_DIR: "/app/crs" REQUEST_LOG_FORMAT: "json" ports: - "8101:8101" - "10000:10000" volumes: - ./crs:/app/crs - ./circuits:/app/circuits:ro - ./services/node/config:/app/config:ro healthcheck: test: ["CMD-SHELL", "curl -fsS http://localhost:8101/health || (echo 'WARN: mpc-node-0 health check failed' >&2; exit 1)"] interval: 5s timeout: 3s retries: 30 # co-noir key generation + CRS load can take 30-60s on a cold start; # failures during this window do not count against `retries`. start_period: 60s <<: *json-logging # MPC Node 1 mpc-node-1: build: context: . dockerfile: services/node/Dockerfile environment: NODE_ID: "1" PORT: "8102" MPC_PORT: "10001" PARTY_CONFIG: "/app/config/party_1.toml" CIRCUIT_DIR: "/app/circuits" CRS_DIR: "/app/crs" REQUEST_LOG_FORMAT: "json" ports: - "8102:8102" - "10001:10001" volumes: - ./crs:/app/crs - ./circuits:/app/circuits:ro - ./services/node/config:/app/config:ro healthcheck: test: ["CMD-SHELL", "curl -fsS http://localhost:8102/health || (echo 'WARN: mpc-node-1 health check failed' >&2; exit 1)"] interval: 5s timeout: 3s retries: 30 # co-noir key generation + CRS load can take 30-60s on a cold start; # failures during this window do not count against `retries`. start_period: 60s <<: *json-logging # MPC Node 2 mpc-node-2: build: context: . dockerfile: services/node/Dockerfile environment: NODE_ID: "2" PORT: "8103" MPC_PORT: "10002" PARTY_CONFIG: "/app/config/party_2.toml" CIRCUIT_DIR: "/app/circuits" CRS_DIR: "/app/crs" REQUEST_LOG_FORMAT: "json" ports: - "8103:8103" - "10002:10002" volumes: - ./crs:/app/crs - ./circuits:/app/circuits:ro - ./services/node/config:/app/config:ro healthcheck: test: ["CMD-SHELL", "curl -fsS http://localhost:8103/health || (echo 'WARN: mpc-node-2 health check failed' >&2; exit 1)"] interval: 5s timeout: 3s retries: 30 # co-noir key generation + CRS load can take 30-60s on a cold start; # failures during this window do not count against `retries`. start_period: 60s <<: *json-logging # Standby MPC nodes (Issue #95 — 5-node committee, N=5 / threshold=3). # # co-noir's REP3 protocol is fixed at exactly 3 active parties per session # (see infrastructure/helm/mpc-node/values.yaml) — that "3" doesn't change. # What "N=5, threshold=3" adds is redundancy: 2 extra pre-provisioned nodes # that hold the *same* identity/config as an existing role (0 or 1) and can # be promoted to take that role's place if its primary container fails, # without a re-key ceremony. They are not started by default (`standby` # profile) — bring one up with `scripts/promote-mpc-standby.sh `, # which stops the failed primary and starts its standby in its place. # See docs/mpc-committee-resilience.md. mpc-node-3: profiles: ["standby"] build: context: . dockerfile: services/node/Dockerfile environment: NODE_ID: "0" # standby for role 0 — same identity as mpc-node-0 PORT: "8104" MPC_PORT: "10000" PARTY_CONFIG: "/app/config/party_0.toml" CIRCUIT_DIR: "/app/circuits" CRS_DIR: "/app/crs" REQUEST_LOG_FORMAT: "json" ports: - "8104:8104" volumes: - ./crs:/app/crs - ./circuits:/app/circuits:ro - ./services/node/config:/app/config:ro healthcheck: test: ["CMD-SHELL", "curl -fsS http://localhost:8104/health || (echo 'WARN: mpc-node-3 (standby for role 0) health check failed' >&2; exit 1)"] interval: 5s timeout: 3s retries: 30 start_period: 60s <<: *json-logging mpc-node-4: profiles: ["standby"] build: context: . dockerfile: services/node/Dockerfile environment: NODE_ID: "1" # standby for role 1 — same identity as mpc-node-1 PORT: "8105" MPC_PORT: "10001" PARTY_CONFIG: "/app/config/party_1.toml" CIRCUIT_DIR: "/app/circuits" CRS_DIR: "/app/crs" REQUEST_LOG_FORMAT: "json" ports: - "8105:8105" volumes: - ./crs:/app/crs - ./circuits:/app/circuits:ro - ./services/node/config:/app/config:ro healthcheck: test: ["CMD-SHELL", "curl -fsS http://localhost:8105/health || (echo 'WARN: mpc-node-4 (standby for role 1) health check failed' >&2; exit 1)"] interval: 5s timeout: 3s retries: 30 start_period: 60s <<: *json-logging # Coordinator (orchestrates MPC + serves API to web app) coordinator: build: context: . dockerfile: services/coordinator/Dockerfile env_file: - path: .env.local required: false environment: MPC_NODE_0: "http://mpc-node-0:8101" MPC_NODE_1: "http://mpc-node-1:8102" MPC_NODE_2: "http://mpc-node-2:8103" SOROBAN_RPC: "http://soroban:8000/soroban/rpc" CIRCUIT_DIR: "/app/circuits" CRS_DIR: "/app/crs" BIND_ADDR: "0.0.0.0:8080" REQUEST_LOG_FORMAT: "json" ports: - "8080:8080" volumes: - ./crs:/app/crs - ./circuits:/app/circuits:ro depends_on: soroban: condition: service_healthy mpc-node-0: condition: service_healthy mpc-node-1: condition: service_healthy mpc-node-2: condition: service_healthy healthcheck: test: ["CMD-SHELL", "curl -fsS http://localhost:8080/api/health || (echo 'WARN: coordinator health check failed' >&2; exit 1)"] interval: 5s timeout: 3s retries: 30 start_period: 20s <<: *json-logging # Web app # app is deployed on Vercel — run locally with: cd app && npm run dev