> ## Documentation Index
> Fetch the complete documentation index at: https://docs.darkpool.fun/llms.txt
> Use this file to discover all available pages before exploring further.

# Docker Compose

> One image, multiple services. Bring the whole backend up in one command.

## Stack

`docker-compose.yml` defines:

| Service        | Role                                | Port                          | Profile                |
| -------------- | ----------------------------------- | ----------------------------- | ---------------------- |
| `redis`        | quote cache + pubsub + sponsor caps | 6379                          | default                |
| `postgres`     | indexer destination                 | 5432 (internal) / 5433 (host) | default                |
| `migrate`      | one-shot Drizzle migration          | n/a                           | default                |
| `api`          | Fastify REST + WS                   | 8081                          | default                |
| `indexer`      | Sui event poller                    | n/a                           | default                |
| `quote-worker` | hot-strike quote poller             | n/a                           | default                |
| `resolver`     | OO settlement + auto-settle keeper  | 8082                          | default                |
| `agent-fleet`  | hosted-fleet agent runtime          | 8084                          | `agent-fleet` (opt-in) |

All `@darkpool/*` services run off the **same image**. One `node:22-slim + corepack pnpm + tsx` Dockerfile, no build step, identical runtime to local dev. Compose picks the process via `command:`.

## Bring it up

```bash theme={"system"}
# Infra first (one-time)
docker compose up -d redis postgres
pnpm --filter @darkpool/server db:migrate

# Whole backend (canonical)
docker compose up -d --build
```

The one-shot `migrate` service gates the db consumers via `service_completed_successfully` so api/indexer never start against an unmigrated db.

Verify:

```bash theme={"system"}
curl -s http://localhost:8081/health | jq
docker compose logs -f api      # or indexer, resolver, …
```

## Hosted fleet profile

```bash theme={"system"}
docker compose --profile agent-fleet up -d agent-fleet
# -> fleet runtime on :8084, ticks every agent_runtime row
```

The fleet container needs:

* `AGENT_KEY_ENCRYPTION_KEY`. same value as on `api`. Without it, decryption fails.
* `FLEET_REGISTRY_TOKEN`. Bearer for `GET /v1/agents/fleet`. Same value as on `api`.
* `SPONSOR_KEY`. falls back to `RESOLVER_KEY`. Pays gas for every agent tick.
* `LLM_API_KEY` + optional `GEMINI_API_KEY`. for the per-agent model routing.

One fleet container can tick dozens of agents. Scale horizontally only if Groq RPM becomes the bottleneck.

## After editing backend code

```bash theme={"system"}
docker compose up -d --build
```

## Stop everything

```bash theme={"system"}
docker compose down
```

## Reset the database

```bash theme={"system"}
docker compose down
docker volume rm darkpool_postgres-data
docker compose up -d postgres
pnpm --filter @darkpool/server db:migrate
```

## Networking

* Same repo-root `.env` via `env_file:`.
* Only `REDIS_URL` (`redis://redis:6379`), `DATABASE_URL` (`postgres://…:5432/darkpool`), and the fleet's `SERVER_HTTP_URL=http://api:8081` are overridden per-service for in-network DNS.
* `.binary-markets/` is mounted **read-only** into api + indexer so registry lookups resolve.
* `packageManager: pnpm@8.13.1` is pinned (9.0.0 made corepack pick a pnpm that can't frozen-install the repo's v6 lockfile).

## Healthchecks

* `api` healthcheck = `node -e fetch(...)` (slim has no curl). Gates the fleet via `service_healthy`.
* `agent-fleet` waits for `${SERVER_URL}/health` (60s deadline) before its first poll, which covers host-mode boots too.

## Dockerfile

```Dockerfile theme={"system"}
FROM node:22-slim
WORKDIR /app
RUN corepack enable
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
COPY packages/ ./packages/
COPY scripts/ ./scripts/
COPY tsconfig.base.json ./
RUN pnpm install --frozen-lockfile
# command is provided by compose
```

No `pnpm build` step. Every service runs via `tsx` directly off source. Identical to local dev, which means one less thing to break.

## Edge hardening (production)

`GET /v1/agents/fleet` returns decrypted agent secrets. Defense in depth:

1. `FLEET_REGISTRY_TOKEN` Bearer in `Authorization` header (enforced by Fastify route).
2. nginx `location /v1/agents/fleet { deny all; }` on the public-facing reverse proxy.
3. Fleet container talks to api via in-network DNS (`http://api:8081`), never the public endpoint.
