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

# Data layer: PostgreSQL, Redis and MongoDB

> What each store holds on a self-hosted install, which can be external, and how to bring your own.

On Hybrid and Air-Gapped installs the data tier runs in your cluster. PostgreSQL and Redis can be replaced by managed services you already run. On xpander cloud, xpander operates it.

![Diagram](https://docs.xpander.ai/images/diagrams/run-integrations-data-layer-f6d4ea56.svg)

## What each store holds

| Store | Holds | External mode |
| - | - | - |
| PostgreSQL 16 | Execution records and activity threads, shared memories, and one schema per agent | Yes |
| Redis Stack (`redis-stack-server`) | Queues, cache, and knowledge-base vectors as RediSearch (`FT.*`) indexes | Yes, with the search module |
| MongoDB | Catalog and monitoring stores | No. Always a chart-managed StatefulSet |
| Supabase's own PostgreSQL 15 | Sign-in, storage metadata. Requires extensions managed services do not ship | No. Always in-cluster |

By default, all stores are chart-managed StatefulSets on your SSD StorageClass (`--storage-class <name>` to pick one). Production volumes on Air-Gapped: xpander's PostgreSQL 50 GiB, Supabase PostgreSQL 50 GiB, Supabase Storage 50 GiB and up, MongoDB 20 GiB, Redis 8 GiB, registry mirror about 50 GiB.

## Tables and collections

Reference for platform teams who need to look inside.

### Supabase's PostgreSQL (database `postgres`)

| Tables | Hold |
| - | - |
| `ai_agents` | The agent record: name, instructions, model and harness settings |
| `agent_executions_history` | Tasks: `status`, `internal_status`, `source_client`, the `is_gateway` and `is_async` flags, `title`, `channel` |
| `connector`, `connector_status_updates` | API connectors and the status changes of their creation pipeline |
| `agent_asset_binding` | Skill bindings: `asset_type`, `auth_mode`, `policy` |
| `organization_api_keys` | API keys as prefix, last four characters, hash and a `secret_ref`; never the key itself |
| `llm_providers`, `model_prices`, `byok_model_prices` | Provider entries and the prices per model, xpander's and the ones you set |
| `live_surfaces` | Live surfaces |
| `organization_environments` | Locations |
| `user_groups`, `group_members`, `user_group_oidc_links`, `organization_oidc_groups`, `organization_oidc_group_settings`, `organization_oidc_config` | User groups and their members, the SSO group links, the group values seen at sign-in, the groups-claim settings, the OIDC configuration |
| `vault_secret`, with `vault.secrets` and `vault.decrypted_secrets` | Credentials, through Supabase Vault |
| `auth.*` | Sign-in (GoTrue) |
| `storage.*` | File metadata |
| `spend_watchdog.*` | Budget enforcement |

### xpander's PostgreSQL 16 (database `xpander`)

| Schema | Holds |
| - | - |
| `xp_tiered` | `executions`: one row per execution with `execution_id`, `agent_id` and `data`, the full execution JSON, including `harness_cli`, `llm_model_name`, `mcp_servers` and any `hitl_request`. `activity_threads`: the threads |
| `xp_shared` | `memories` and `memory_settings` |
| `ag_<agent id>` | One schema per agent: sessions, memories, approvals, schedules and their runs, metrics, knowledge, learnings, evaluation runs, and a schema-version table |

### MongoDB

| Collection | Holds |
| - | - |
| `monitoring.audit` | The audit log. Fields `organization_id`, `user_id`, `event_type`, `timestamp`, `metadata`, `expires_at`. Event types include `add_skill`, `change_llm_secrets`, `change_oidc_settings`, `change_org_default_llm_config`, `connect_connector`, `create_agent`, `create_api_key`, `create_knowledge_base`, `delete_agent`, `deploy_agent`, `read_secret`, `update_agent`, `update_secret`, `use_secret`, `user_login`, `user_logout`, `workspace_tool_invoked` |
| `monitoring.cost_ledger` | One row per execution at token level: `model_id`, `tier`, input, output and cache tokens, `cost_usd`, `nominal_cost_usd`, `is_byok` |
| `monitoring.monitor` | Monitoring records |
| `billing.wallet_ledger`, `billing.wallet_usage_rollups` | Credits in millicredits per meter, with tokens, model and cycle, and their rollups |
| `catalog.openapi_specs`, `catalog.operations`, `catalog.schemas` | The skill catalog's OpenAPI specs, their operations and schemas |

### Looking inside from the cluster

Passwords come from the `xpander-generated-keys` secret, and nothing below prints one.

```bash theme={"dark"}
# xpander's PostgreSQL. The postgres Service resolves to the in-cluster StatefulSet or to your external host.
PGPASS=$(kubectl -n xpander get secret xpander-generated-keys -o jsonpath='{.data.POSTGRES_PASSWORD}' | base64 -d)
kubectl -n xpander exec supabase-db-0 -c postgres -- env PGPASSWORD="$PGPASS" \
  psql -h postgres -U xpander -d xpander -c '\dn'
kubectl -n xpander exec supabase-db-0 -c postgres -- env PGPASSWORD="$PGPASS" \
  psql -h postgres -U xpander -d xpander -c 'select execution_id, agent_id from xp_tiered.executions limit 5'
```

```bash theme={"dark"}
# Supabase's PostgreSQL, local to the same pod.
SBPASS=$(kubectl -n xpander get secret xpander-generated-keys -o jsonpath='{.data.SUPABASE_DB_PASSWORD}' | base64 -d)
kubectl -n xpander exec supabase-db-0 -c postgres -- env PGPASSWORD="$SBPASS" \
  psql -h 127.0.0.1 -U postgres -d postgres -c '\dt public.*'
```

```bash theme={"dark"}
# MongoDB, from inside mongodb-0, with the root credentials the chart generated.
kubectl -n xpander exec -it mongodb-0 -- mongosh -u <MONGO_ROOT_USER> -p <MONGO_ROOT_PASSWORD> --authenticationDatabase admin
```

At the `mongosh` prompt:

```javascript theme={"dark"}
db.getSiblingDB("monitoring").audit.find({}, { event_type: 1, timestamp: 1, user_id: 1 }).sort({ timestamp: -1 }).limit(10)
db.getSiblingDB("monitoring").cost_ledger.find().sort({ _id: -1 }).limit(3)
```

## Bringing your own PostgreSQL and Redis

Use installer flags or values, and pass passwords with `--set` so they never land in Git:

```bash theme={"dark"}
bash install.sh --edition airgap ... \
  --pg-mode external --pg-dsn 'postgresql://xpander:password@db.internal:5432/xpander' \
  --redis-mode external --redis-endpoint <clustercfg endpoint, no port> \
  --redis-tls --redis-cluster --redis-username <acl-user> --redis-password <pass>
```

```yaml theme={"dark"}
postgres:
  mode: external
  external:
    host: postgres.internal.acme.example
    port: 5432
redis:
  mode: external
global:
  redis:
    endpoint: <clustercfg endpoint, no port>
    clusterMode: true
    auth:
      username: <acl-user>
```

* **PostgreSQL:** create the role and database before the install. Keep `CREATEDB` on the role. Agent data lands as one schema per agent (`ag_<agent id>`) inside the `xpander` database. `keygen.overrides.POSTGRES_PASSWORD` is how xpander learns the password you set.

  ```sql theme={"dark"}
  CREATE ROLE xpander LOGIN PASSWORD '<password>' CREATEDB;
  CREATE DATABASE xpander OWNER xpander;
  ```

* **Valkey or MemoryDB:** the endpoint must ship the RediSearch commands, or knowledge bases accept writes and return nothing. On MemoryDB that is Valkey 7.3 on the parameter group with the search module.

* **Probes:** the installer runs `pg_isready` and `redis-cli ping` from a pod in your namespace before installing. A failed probe asks you what to do instead of aborting.

RDS and MemoryDB sizes from a reference install are on [Air-gapped: data tier](https://docs.xpander.ai/self-hosted/airgap.md).

MongoDB and Supabase's PostgreSQL always run in-cluster. Back them up with your cluster's volume snapshots.

## Next steps

> **[Plan the data tier for Air-Gapped](https://docs.xpander.ai/self-hosted/airgap.md)**
>
> RDS and MemoryDB sizing

> **[Plan file storage](https://docs.xpander.ai/run/integrations/file-storage.md)**
>
> Workspaces, uploads, files


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.