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

# Deploy the brain

> Start a local brain in two commands, or bring up the reference Compose stack with ordinary, self-hosted Supabase, or hosted Postgres.

## Local brain (no auth, SQLite)

Prerequisites: Python 3.11+, a POSIX shell; OpenSSH and `rsync` only if you'll use sandboxes; provider credentials only if you'll provision real ones.

<Steps>
  <Step title="Install and start">
    ```bash theme={"theme":{"light":"github-light","dark":"dark-plus"}}
    cd Merv/merv
    python3 -m venv .venv
    .venv/bin/pip install -r requirements.txt
    ./bin/merv-http --host 127.0.0.1 --port 8787
    ```

    Auto-reload while editing backend code: `python3 scripts/dev_http_reload.py --host 127.0.0.1 --port 8787`.
  </Step>

  <Step title="Check it">
    ```bash theme={"theme":{"light":"github-light","dark":"dark-plus"}}
    curl -s http://127.0.0.1:8787/health
    curl -s http://127.0.0.1:8787/api/meta
    ```
  </Step>

  <Step title="Point a client at it">
    Local mode is auth-free. For a headless client or the runner:

    ```bash theme={"theme":{"light":"github-light","dark":"dark-plus"}}
    ./bin/merv-client configure --control-url http://127.0.0.1:8787
    ./bin/merv-client env      # prints the MCP http entry for http://127.0.0.1:8787/mcp
    ```

    For an interactive client, register the MCP server URL `http://127.0.0.1:8787/mcp` the way that client registers any HTTP MCP server; there is no OAuth to complete.
  </Step>
</Steps>

## Reference Compose stack

From `merv/`. The base file runs `control` (the brain), `minio`, and `mgmtkey` (a development-only management SSH key). Add one database overlay.

<Tabs>
  <Tab title="Postgres">
    ```bash theme={"theme":{"light":"github-light","dark":"dark-plus"}}
    docker compose \
      -f deploy/docker-compose.yml \
      -f deploy/docker-compose.postgres.yml \
      up --build -d
    curl -s http://127.0.0.1:8787/api/meta
    ```

    Binds Postgres to `127.0.0.1:5432` and supplies `MERV_DB_URL` to control. Override `MERV_POSTGRES_PASSWORD` and `MERV_POSTGRES_PORT` as needed.
  </Tab>

  <Tab title="Self-hosted Supabase">
    A deliberately small stack: Supabase Postgres, `postgres-meta`, and Studio — no GoTrue, PostgREST, Realtime, Kong, or Storage.

    ```bash theme={"theme":{"light":"github-light","dark":"dark-plus"}}
    cp deploy/supabase.env.example deploy/.env.supabase.local   # replace every CHANGE_ME (use `openssl rand -hex 32`)
    docker compose --env-file deploy/.env.supabase.local \
      -f deploy/docker-compose.yml \
      -f deploy/docker-compose.supabase.yml \
      up --build -d
    ```

    Studio is at `http://127.0.0.1:55433` (Table Editor → `public`). On a remote host keep ports on loopback and tunnel: `ssh -N -L 55433:127.0.0.1:55433 -L 55432:127.0.0.1:55432 USER@HOST`. Don't bind to `0.0.0.0` — this minimal Studio has no auth layer.
  </Tab>

  <Tab title="Hosted or external Postgres">
    Create a **dedicated** database project for Merv data (don't reuse the auth project's database — Merv owns tables in `public` and runs DDL at startup).

    1. In the SQL editor run `deploy/supabase/hosted-bootstrap.sql` (set the password), then as `merv_app` run `deploy/supabase/app-default-privileges.sql`.
    2. Set `MERV_DB_URL` to the direct endpoint or the **session pooler on 5432**, with `sslmode=require`. Never the transaction pooler on 6543 — Merv holds session advisory locks at startup.
    3. Keep every replica's DSN byte-for-byte identical (the cross-replica lock key derives from it).
    4. Start the base Compose file (no overlay).

    `merv_app` must own its tables and keep `USAGE, CREATE` on `public`. A separate schema isn't supported.
  </Tab>
</Tabs>

### Preflight the database

```bash theme={"theme":{"light":"github-light","dark":"dark-plus"}}
docker compose --env-file deploy/.env.supabase.local \
  -f deploy/docker-compose.yml -f deploy/docker-compose.supabase.yml \
  run --rm --no-deps --entrypoint python control deploy/db_preflight.py   # add --require-tls for hosted
```

Checks connectivity, read/write, `public` privileges, transactional DDL, TLS, and that session advisory locks survive a round trip. Rejects the transaction-pooler port. Never prints the password.

### Required hosted configuration

`merv-control` forces `MERV_MODE=control`. Startup requires:

| Variable                                          | Purpose                                                            |
| ------------------------------------------------- | ------------------------------------------------------------------ |
| `MERV_DB_URL`                                     | Postgres record store                                              |
| `MERV_BLOB_BUCKET` + `AWS_*`                      | Durable submitted-byte blob store (MinIO in the reference stack)   |
| `MERV_MGMT_KEY_PATH`                              | Mounted **private-key** file readable only by the control process  |
| `MERV_MGMT_PUBLIC_KEY` or an adjacent `<key>.pub` | Its public half                                                    |
| `MERV_WAIT_SECRET`                                | ≥ 32 bytes; signs long-run `wait_url`s — the boot fails without it |

`MERV_ADMIN_TOKEN` doesn't gate startup, but without it every `/api/admin/*` call is denied.

Optional heavy object storage: `MERV_STORAGE_PROVIDER` plus bucket/credentials; `MERV_STORAGE_MAX_UPLOAD_BYTES` (default 50 GiB) caps per-object size, overridable per project below that ceiling. `MERV_REQUIRE_SANDBOX_BACKEND=1` refuses to start if the selected provider is unhealthy. `.env.example` lists every variable; legacy `RESEARCH_PLUGIN_*` names still work as fallbacks (`MERV_*` wins).

The brain serves plain HTTP on 8787 — terminate TLS at a proxy or load balancer. Next: [Configure auth](/docs/merv/self-host/configure-auth).
