# Air-gapped single-node topology. The network segmentation IS the security # model (spec §15): # edge — host-published entry points only (frontend, server API) # core — internal: server <-> postgres # sandbox_net — internal, NO egress: agent sandboxes (joined in P2) # secrets_net — internal: secret broker + server only (joined in P2) # Images are preloaded from the signed offline bundle; nothing pulls at # install time. name: clawmates networks: edge: {} core: internal: true sandbox_net: internal: true secrets_net: internal: true # Server <-> socket proxy only; the raw Docker socket never reaches the # server container. engine_net: internal: true volumes: pgdata: {} # Unix socket + master key shared ONLY between server and broker — # the unix-socket equivalent of the K8s sidecar topology. broker_run: {} broker_key: {} # Per-mission repo checkouts + generated PDFs live on the host at # /var/lib/clawmates-missions so the (separately-managed) # clawmates-runtime container can bind-mount the SAME host path and # see the same tree the server wrote to. A named docker volume # would work too but would need volume-name knowledge in the # runtime's spawn command. services: postgres: image: postgres:16-alpine restart: unless-stopped environment: POSTGRES_DB: clawmates POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set in .env} volumes: - pgdata:/var/lib/postgresql/data networks: [core] healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres -d clawmates"] interval: 5s timeout: 3s retries: 12 # Named volumes mount root-owned, but the broker runs as uid 10001 from # a scratch image (no shell to chown itself). This one-shot prepares the # socket + key volumes, then exits. `on-failure` so a transient chown error # retries instead of wedging boot (broker/server depend on it completing). volume-init: image: busybox:1.36 command: ["sh", "-c", "chown -R 10001:10001 /run/clawmates /etc/clawmates-broker"] user: "0:0" volumes: - broker_run:/run/clawmates - broker_key:/etc/clawmates-broker restart: on-failure:5 # The secret broker: separate process, separate image; credentials # never leave it. Reachable only via the shared unix socket volume. broker: image: clawmates/broker:${CLAWMATES_VERSION:-latest} build: context: ../.. dockerfile: images/broker.Dockerfile restart: unless-stopped environment: CLAWMATES_BROKER_SOCKET: /run/clawmates/broker.sock CLAWMATES_BROKER_KEY_FILE: /etc/clawmates-broker/broker.key CLAWMATES_DATABASE__URL: postgres://postgres:${POSTGRES_PASSWORD:?set in .env}@postgres:5432/clawmates volumes: - broker_run:/run/clawmates - broker_key:/etc/clawmates-broker # `core` stays for postgres access; `edge` grants OUTBOUND internet so the # broker can act as the §14 door (fetch Gitea/GitHub/GitLab repo lists, # forward Slack sends, etc.) without leaking any credential back to # cm-api. The broker still doesn't accept inbound TCP — it only listens # on the unix socket inside broker_run. networks: [core, edge] depends_on: postgres: condition: service_healthy volume-init: condition: service_completed_successfully # Allow-listed Docker API (§15 blast-radius cap): the server can # create/exec/stop/remove sandbox containers and NOTHING else — no # image builds, no networks, no secrets, no volumes. Proven by # crates/cm-sandbox/tests/socket_proxy.rs against this exact allowlist. socket-proxy: image: tecnativa/docker-socket-proxy:0.3 restart: unless-stopped environment: CONTAINERS: 1 POST: 1 EXEC: 1 DELETE: 1 VERSION: 1 # NETWORKS grant (C3): mission_runtime::ensure_container needs # to attach per-mission runtime containers to the clawmates_edge # network for provider egress, in addition to creating them on # clawmates_core. NETWORKS: 1 volumes: - /var/run/docker.sock:/var/run/docker.sock:ro networks: [engine_net] server: image: clawmates/server:${CLAWMATES_VERSION:-latest} build: context: ../.. dockerfile: images/server.Dockerfile restart: unless-stopped # Operator knobs (bootstrap owner, LLM provider/key overrides, OTLP) come # from .env — only the keys you actually set are injected, so unset # options stay absent rather than overriding clawmates.toml with "". env_file: - path: .env required: false environment: CLAWMATES_CONFIG: /etc/clawmates/clawmates.toml CLAWMATES_DATABASE__URL: postgres://postgres:${POSTGRES_PASSWORD:?set in .env}@postgres:5432/clawmates DOCKER_HOST: tcp://socket-proxy:2375 CLAWMATES_MISSIONS_ROOT: /var/lib/clawmates-missions CLAWMATES_RUNTIME_CONTAINER: ${CLAWMATES_RUNTIME_CONTAINER:-clawmates-runtime} volumes: - ./clawmates.toml:/etc/clawmates/clawmates.toml:ro - broker_run:/run/clawmates # Per-mission repo checkouts. Bind-mounted host path so the # separately-managed clawmates-runtime container can see the # same trees at the same path when it exec's for scans/benches. - /var/lib/clawmates-missions:/var/lib/clawmates-missions networks: [edge, core, engine_net] ports: - "8080:8080" depends_on: postgres: condition: service_healthy frontend: image: clawmates/frontend:${CLAWMATES_VERSION:-latest} build: context: ../.. dockerfile: images/frontend.Dockerfile restart: unless-stopped # AUTH_MODE + CLERK_* (when running Clerk) come from .env; see docs/clerk.md. env_file: - path: .env required: false environment: API_ORIGIN: http://server:8080 networks: [edge] ports: - "3000:3000" depends_on: - server