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

# Relational Databases

> Configure relational databases for metadata and state storage in Cognee

Relational databases store metadata, document information, and system state in Cognee. They track documents, chunks, and provenance (where data came from and how it's linked).

<Info>
  **New to configuration?**

  See the [Setup Configuration Overview](./overview) for the complete workflow:

  install extras → create `.env` → choose providers → handle pruning.
</Info>

## Supported Providers

Cognee supports these relational database options:

* **SQLite** — File-based database, works out of the box (default)
* **Postgres** — Production-ready database for multi-process concurrency
* **Turso (libSQL)** — A SQLite-compatible drop-in with optional embedded-replica sync for a hosted Turso database

## Configuration

<Accordion title="Environment Variables">
  Set these environment variables in your `.env` file:

  * `DB_PROVIDER` — The database provider (sqlite, postgres, turso)
  * `DB_NAME` — Database name
  * `DB_HOST` — Database host (Postgres only)
  * `DB_PORT` — Database port (Postgres only)
  * `DB_USERNAME` — Database username (Postgres only)
  * `DB_PASSWORD` — Database password (Postgres only)
  * `DB_TURSO_URL` — Remote Turso database URL, e.g. `libsql://<your-db>.turso.io` (Turso remote mode only; leave unset for a local libSQL file)
  * `DB_TURSO_AUTH_TOKEN` — Auth token for the remote Turso database (Turso remote mode only)
</Accordion>

## Setup Guides

<AccordionGroup>
  <Accordion title="SQLite (Default)">
    SQLite is file-based and requires no additional setup. It's perfect for local development and single-user scenarios.

    ```dotenv theme={null}
    DB_PROVIDER="sqlite"
    DB_NAME="cognee_db"
    ```

    **Installation**: SQLite is included by default with Cognee. No additional installation required.

    **Data Location**: Data is stored under the Cognee system directory. You can override the root with `SYSTEM_ROOT_DIRECTORY` in your `.env` file.
  </Accordion>

  <Accordion title="Postgres">
    Postgres is recommended for production environments, multi-process concurrency, or when you need external hosting.

    <Tabs>
      <Tab title=".env">
        Set the connection in your `.env` file:

        ```dotenv theme={null}
        DB_PROVIDER="postgres"
        DB_NAME="cognee_db"
        DB_HOST="127.0.0.1"            # use host.docker.internal when running inside Docker
        DB_PORT="5432"
        DB_USERNAME="cognee"
        DB_PASSWORD="cognee"
        ```
      </Tab>

      <Tab title="Python">
        Instead of (or in addition to) the `.env` file, set the same values at runtime with `cognee.config.set_relational_db_config()`. Call it before any `add()`, `cognify()`, or `remember()` so the connection is used from the first operation:

        ```python theme={null}
        import asyncio
        import cognee

        async def main():
            cognee.config.set_relational_db_config(
                {
                    "db_provider": "postgres",
                    "db_name": "cognee_db",
                    "db_host": "127.0.0.1",   # host.docker.internal inside Docker
                    "db_port": "5432",
                    "db_username": "cognee",
                    "db_password": "cognee",
                }
            )

            await cognee.remember(["Cognee stores its metadata in Postgres."])
            print(await cognee.recall(query_text="Where is metadata stored?"))

        asyncio.run(main())
        ```

        The dictionary keys match the `DB_*` variables in the `.env` tab. To also route embeddings and the graph into the same Postgres instance, pair this with [`set_vector_db_config({"vector_db_provider": "pgvector"})`](/setup-configuration/vector-stores) and `GRAPH_DATABASE_PROVIDER="postgres"`.
      </Tab>
    </Tabs>

    **Installation**: Install the Postgres extras:

    ```bash theme={null}
    pip install "cognee[postgres]"
    # or for binary version
    pip install "cognee[postgres-binary]"
    ```

    **Docker Setup**: Use the built-in Postgres service:

    ```bash theme={null}
    docker compose --profile postgres up -d
    ```

    **Docker Networking**: When running Cognee in Docker and Postgres on your host, set:

    ```dotenv theme={null}
    DB_HOST="host.docker.internal"
    ```

    **Migrations**: The Cognee API server runs startup migrations during its lifespan startup. For standalone scripts, CI, or deployments where you manage database lifecycle explicitly, run [`run_startup_migrations`](/python-api/run-migrations) before serving traffic — especially the first time you point Cognee at a fresh external Postgres database, or after upgrading the `cognee` package:

    ```python theme={null}
    import cognee

    await cognee.run_startup_migrations()
    ```
  </Accordion>

  <Accordion title="Neon Postgres">
    Neon works with Cognee through the normal `postgres` relational provider. Cognee can use Neon Postgres for relational metadata, document information, chunks, pgvector storage, and Postgres graph state. Neon requires SSL/TLS for connections.

    Before configuring Cognee, create a Neon project, branch, database, and role, then copy the connection string from **Connection Details** in the Neon dashboard. A Neon connection string usually looks like this:

    ```text theme={null}
    postgresql://user:password@ep-example.us-east-2.aws.neon.tech/neondb?sslmode=require
    ```

    If your deployment uses `DATABASE_URL`, set it to the Neon connection string:

    ```dotenv theme={null}
    DATABASE_URL="postgresql://user:password@ep-example.us-east-2.aws.neon.tech/neondb?sslmode=require"
    ```

    If you prefer split settings, map the same connection string into Cognee's `DB_*` variables:

    ```dotenv theme={null}
    DB_PROVIDER="postgres"
    DB_NAME="neondb"
    DB_HOST="ep-example.us-east-2.aws.neon.tech"
    DB_PORT="5432"
    DB_USERNAME="user"
    DB_PASSWORD="password"
    DATABASE_CONNECT_ARGS='{"ssl": "require", "timeout": 10}'
    ```

    Install Postgres support in the environment where Cognee runs:

    ```bash theme={null}
    pip install "cognee[postgres]"
    # or
    pip install "cognee[postgres-binary]"
    ```

    When using split settings, express Neon's `?sslmode=require` parameter as `DATABASE_CONNECT_ARGS='{"ssl": "require"}'`. `DATABASE_CONNECT_ARGS` must be valid JSON. Cognee forwards these arguments to the main relational engine, per-dataset PGVector engines, and the `GRAPH_DATABASE_PROVIDER="postgres"` graph engine.

    One Neon database can also back PGVector and the Postgres graph store:

    ```dotenv theme={null}
    VECTOR_DB_PROVIDER="pgvector"
    GRAPH_DATABASE_PROVIDER="postgres"
    ```

    For PGVector, enable the extension once in the Neon database:

    ```sql theme={null}
    CREATE EXTENSION IF NOT EXISTS vector;
    ```

    **Application database vs source database**: `DATABASE_URL` or the `DB_*` variables configure Cognee's own application database. Cognee uses this database for its internal metadata and state. To ingest an external Postgres database as data, pass that source database connection to `cognee.add()` instead:

    ```python theme={null}
    await cognee.add(
        "postgresql://user:pass@host:5432/source_db",
        dataset_name="postgres_data",
    )
    ```

    That source database is separate from Cognee's application database.

    **Direct vs pooled Neon hosts**: use the direct Neon host for setup, schema migrations, and default Cognee connections. The direct hostname does not contain `-pooler`:

    ```dotenv theme={null}
    DATABASE_URL="postgresql://user:password@ep-example.us-east-2.aws.neon.tech/neondb?sslmode=require"
    ```

    Neon pooled hosts route through PgBouncer and contain `-pooler` in the hostname:

    ```dotenv theme={null}
    DATABASE_URL="postgresql://user:password@ep-example-pooler.us-east-2.aws.neon.tech/neondb?sslmode=require"
    ```

    Prefer the direct host unless you have a specific need for pooled, high-concurrency application traffic after setup. Run migrations against the direct endpoint only. Neon PgBouncer does not support every session-level operation migrations and maintenance may rely on, and Cognee maintenance operations such as `CREATE DATABASE` and `DROP DATABASE` cannot run through the pooler. If setup or migrations fail on a `-pooler` host, switch to the direct host and retry.

    You can verify the same credentials with `psql`:

    ```bash theme={null}
    psql "postgresql://user:password@ep-example.us-east-2.aws.neon.tech/neondb?sslmode=require"
    ```

    If you use Cognee's relational database migration features with Neon, keep the application database and migration source database separate. Use direct hosts for both while running migrations:

    ```dotenv theme={null}
    # Application DB: Cognee's internal metadata store
    DATABASE_URL="postgresql://user:password@ep-example.us-east-2.aws.neon.tech/neondb?sslmode=require"

    # Migration DB: source data to convert into Cognee's knowledge graph
    MIGRATION_DB_PROVIDER="postgres"
    MIGRATION_DB_HOST="ep-source.us-east-2.aws.neon.tech"
    MIGRATION_DB_PORT="5432"
    MIGRATION_DB_USERNAME="readonly_user"
    MIGRATION_DB_PASSWORD="readonly_password"
    MIGRATION_DB_NAME="source_app_db"
    ```

    Use a different `MIGRATION_DB_NAME` unless you intentionally want to migrate Cognee's own internal tables into the knowledge graph.
  </Accordion>

  <Accordion title="Turso (libSQL)">
    A libSQL database file *is* a SQLite file, so Turso is a drop-in for the SQLite backend: Cognee talks to it through the same `aiosqlite` driver, the same sqlite dialect, and the same sqlite-dialect Alembic migrations. No migration changes are needed when switching between SQLite and Turso.

    **Installation**: Install the Turso extra:

    ```bash theme={null}
    pip install "cognee[turso]"
    ```

    **Local / embedded**: A libSQL file stored on disk under the Cognee data directory (named by `DB_NAME`). This is identical to the SQLite backend:

    ```dotenv theme={null}
    DB_PROVIDER="turso"
    DB_NAME="cognee_db"
    ```

    **Remote (embedded replica)**: Set `DB_PROVIDER="turso"` and point at a hosted Turso database with `DB_TURSO_URL` and `DB_TURSO_AUTH_TOKEN`:

    ```dotenv theme={null}
    DB_PROVIDER="turso"
    DB_NAME="cognee_db"
    DB_TURSO_URL="libsql://<your-db>.turso.io"
    DB_TURSO_AUTH_TOKEN="<your-token>"
    ```

    In remote mode Cognee reads and writes a fast local replica through `aiosqlite` exactly as in local mode, while `libsql-experimental` handles embedded-replica sync with the hosted primary. The replica is seeded from the primary before first use, and Cognee attempts a sync after each write within the operation. Seeding and syncing run off the event loop and are best-effort: a slow or unreachable primary is logged and never blocks or breaks a database operation, and the local replica stays usable.

    <Note>
      The remote write path applies through `aiosqlite`; whether libSQL's sync propagates those writes to the hosted primary depends on the driver's replica write-capture and should be confirmed against a live Turso database. The local drop-in path is fully exercised offline.
    </Note>

    <Info>
      Turso is a SQLite-compatible drop-in for Cognee's core relational backend, but DLT-based ingestion connectors do not yet treat `DB_PROVIDER="turso"` the same as `sqlite`. The main add → cognify → search pipeline is covered; DLT connector support needs a follow-up.
    </Info>
  </Accordion>
</AccordionGroup>

## Advanced Options

<Accordion title="Migration Configuration">
  The `MIGRATION_DB_*` variables point to a **source** database that you want to extract and migrate **into** Cognee's knowledge graph. This is entirely separate from the application database (`DB_*`) that Cognee uses for its own internal metadata and state.

  | Variable | Application DB (`DB_*`)                        | Migration DB (`MIGRATION_DB_*`)           |
  | -------- | ---------------------------------------------- | ----------------------------------------- |
  | Purpose  | Cognee's internal metadata store               | Source data you want converted to a graph |
  | Contains | Cognee's own tables (documents, chunks, state) | Your application's tables and rows        |

  **Does the migration DB need to be a different database than the application DB?**

  In practice, use a different database (different `DB_NAME` / `MIGRATION_DB_NAME`) unless you intentionally want to migrate Cognee's own internal tables into the knowledge graph. They can still live on the same Postgres server as long as they are different databases.

  <Tabs>
    <Tab title="SQLite Source">
      Use this when your source data is in a SQLite file, regardless of what `DB_PROVIDER` is set to:

      ```dotenv theme={null}
      # Application DB (Cognee's internal store)
      DB_PROVIDER="postgres"
      DB_NAME="cognee_db"
      DB_HOST="127.0.0.1"
      DB_PORT="5432"
      DB_USERNAME="cognee"
      DB_PASSWORD="cognee"

      # Migration DB (your source data — a separate SQLite file)
      MIGRATION_DB_PROVIDER="sqlite"
      MIGRATION_DB_PATH="/path/to/migration/directory"
      MIGRATION_DB_NAME="my_app_data.sqlite"
      ```
    </Tab>

    <Tab title="Same Postgres Server">
      Use this when your source data is in a separate Postgres database on the same server as Cognee's application DB. Set `MIGRATION_DB_NAME` to a **different** database name for the usual case:

      ```dotenv theme={null}
      # Application DB (Cognee's internal store)
      DB_PROVIDER="postgres"
      DB_NAME="cognee_db"
      DB_HOST="127.0.0.1"
      DB_PORT="5432"
      DB_USERNAME="cognee"
      DB_PASSWORD="cognee"

      # Migration DB (your source data — different DB name on the same Postgres server)
      MIGRATION_DB_PROVIDER="postgres"
      MIGRATION_DB_HOST="127.0.0.1"
      MIGRATION_DB_PORT="5432"
      MIGRATION_DB_USERNAME="cognee"
      MIGRATION_DB_PASSWORD="cognee"
      MIGRATION_DB_NAME="my_app_db"   # usually different from DB_NAME above
      ```
    </Tab>

    <Tab title="Different Postgres Server">
      Use this when your source data lives on a separate Postgres instance:

      ```dotenv theme={null}
      # Migration DB (separate Postgres instance)
      MIGRATION_DB_PROVIDER="postgres"
      MIGRATION_DB_HOST="db.example.com"
      MIGRATION_DB_PORT="5432"
      MIGRATION_DB_USERNAME="readonly_user"
      MIGRATION_DB_PASSWORD="readonly_password"
      MIGRATION_DB_NAME="production_db"
      ```
    </Tab>
  </Tabs>

  See the [Relational Database Migration example](/examples/relational-db-migration) for a complete walkthrough of migrating schema and data into a knowledge graph.
</Accordion>

<Accordion title="Managed Postgres with SSL (connect args)">
  Managed Postgres providers (Neon, RDS/Aurora, Azure Database for PostgreSQL) often require SSL. Pass asyncpg/SQLAlchemy connection arguments through the `DATABASE_CONNECT_ARGS` environment variable, which takes a JSON object:

  ```dotenv theme={null}
  DATABASE_CONNECT_ARGS='{"ssl": "require", "timeout": 10}'
  ```

  These connect args are forwarded to Cognee's main relational engine, per-dataset PGVector engines, and the `GRAPH_DATABASE_PROVIDER="postgres"` graph engine. The maintenance engine that runs CREATE/DROP DATABASE also uses the SSL setting. Leaving `DATABASE_CONNECT_ARGS` unset is a no-op, so in-cluster Postgres needs no change.

  The maintenance engine talks to Postgres over asyncpg, which expects an `ssl` key rather than libpq's `sslmode`; if you supply `sslmode`, its value is mapped to asyncpg's `ssl` for maintenance operations. For **Neon** specifically, the maintenance engine also rewrites a `-pooler.` host to its direct endpoint, because CREATE/DROP DATABASE cannot run through Neon's PgBouncer connection pooler.

  The value must be a valid JSON object; invalid JSON raises a configuration error.
</Accordion>

<Accordion title="Backend Access Control">
  Enable per-user dataset isolation for multi-tenant scenarios.

  ```dotenv theme={null}
  ENABLE_BACKEND_ACCESS_CONTROL="true"
  ```

  This feature is available for both SQLite and Postgres.
</Accordion>

## Troubleshooting

<Accordion title="Common Issues">
  **Postgres Connectivity**: Verify the database is listening on `DB_HOST:DB_PORT` and credentials are correct:

  ```bash theme={null}
  psql -h 127.0.0.1 -U cognee -d cognee_db
  ```

  **Docker Networking**: Use `host.docker.internal` for host-to-container access on macOS/Windows.

  **SQLite Concurrency**: SQLite connections now open in WAL (Write-Ahead Logging) journal mode with `synchronous=NORMAL` and a 120-second busy timeout, and the driver connect timeout is also 120 seconds. This lets concurrent writers wait for the write lock (up to the busy timeout) instead of immediately failing, which greatly reduces the `sqlite3.OperationalError: database is locked` errors that could occur under Cognee's parallel `cognify()` writes. No configuration is required — these settings apply automatically to every SQLite connection. WAL mode creates `-wal` and `-shm` sidecar files next to the database file; include them when backing up or copying the database. For heavy multi-user or multi-process workloads, still prefer Postgres.

  **SQLite File Locks on Windows (pruning/deleting)**: When pruning or deleting a SQLite database, Cognee now disposes the cached SQLAlchemy engine (clearing the relational-engine cache and forcing garbage collection) before removing the file, so the underlying connection releases the file handle. If a stubborn Windows file lock still prevents removal after the retries, deletion no longer raises — it logs a warning and continues. In that case, the SQLite file may remain on disk and can be removed manually after the process releases the handle.
</Accordion>

<Accordion title="Neon SSL and connect args">
  Neon requires SSL/TLS. If you use a full Neon connection string, keep `sslmode=require` in the URL:

  ```dotenv theme={null}
  DATABASE_URL="postgresql://user:password@host/neondb?sslmode=require"
  ```

  If you use split `DB_*` settings instead of `DATABASE_URL`, pass SSL through `DATABASE_CONNECT_ARGS`:

  ```dotenv theme={null}
  DB_PROVIDER="postgres"
  DB_NAME="neondb"
  DB_HOST="ep-example.us-east-2.aws.neon.tech"
  DB_PORT="5432"
  DB_USERNAME="user"
  DB_PASSWORD="password"
  DATABASE_CONNECT_ARGS='{"ssl": "require", "timeout": 10}'
  ```

  `DATABASE_CONNECT_ARGS` must be valid JSON. Invalid JSON raises a configuration error before Cognee connects.
</Accordion>

<Accordion title="Missing LLM API key">
  Operations that extract or answer over memory need an LLM provider. If `remember()`, `cognify()`, `recall()`, or related workflows fail because no LLM credentials are configured, set the provider API key in your environment:

  ```dotenv theme={null}
  LLM_API_KEY="sk-..."
  ```

  See [LLM Providers](/setup-configuration/llm-providers) for provider-specific settings.
</Accordion>

<Accordion title="asyncpg prepared-statement / connection-pooler errors">
  On Postgres and PGVector, Cognee connects through the asyncpg driver, which caches prepared statements **per connection**. When you place a **transaction-mode** connection pooler in front of Postgres — PgBouncer in `transaction` mode, or the Supabase / Neon connection poolers — a single client connection is multiplexed across many short-lived server backends. The cached statement names can then collide or vanish between checkouts, surfacing as:

  ```text theme={null}
  asyncpg.exceptions.DuplicatePreparedStatementError: prepared statement "__asyncpg_stmt_1__" already exists
  ```

  or as intermittent `connection is closed` / `InterfaceError` pool errors under concurrency.

  **Preferred fix — use the direct (session-mode) endpoint.** Point `DB_HOST` (and `VECTOR_DB_HOST`) at the direct Postgres endpoint rather than the transaction pooler. Cognee already does this for its own `CREATE`/`DROP DATABASE` maintenance work, rewriting a Neon `-pooler.` host to the direct endpoint, because those statements cannot run through PgBouncer.

  **If you must route through a transaction-mode pooler**, disable asyncpg's statement cache through [`DATABASE_CONNECT_ARGS`](/setup-configuration/relational-databases):

  ```dotenv theme={null}
  DATABASE_CONNECT_ARGS='{"statement_cache_size": 0, "prepared_statement_cache_size": 0}'
  ```

  These connect args are forwarded to the main relational engine, the per-dataset PGVector engines, and the `GRAPH_DATABASE_PROVIDER="postgres"` graph engine, so all three asyncpg connections stop caching prepared statements. You can combine them with the SSL keys in the same JSON object (for example `{"ssl": "require", "statement_cache_size": 0}`).
</Accordion>

<Accordion title="Too many connections (Neon free tier / hosted Postgres limits)">
  When the same Postgres backs relational metadata, PGVector, and the `GRAPH_DATABASE_PROVIDER="postgres"` graph store, Cognee can open more connections than a low connection limit allows — most commonly on Neon's free tier — surfacing as:

  ```text theme={null}
  asyncpg.exceptions.TooManyConnectionsError: sorry, too many clients already
  # or: FATAL: remaining connection slots are reserved for non-replication superuser connections
  ```

  Cognee opens a **separate SQLAlchemy connection pool per engine**, and the `DB_*` / `DATABASE_CONNECT_ARGS` settings are reused across all of them:

  * **Relational engine** — QueuePool with `pool_size=5` and `max_overflow=35` (up to 40 connections), plus `pool_pre_ping=True` and `pool_recycle=280`.
  * **PGVector** — when backend access control is off and the relational provider is Postgres, PGVector **reuses the relational engine** and adds no connections of its own. It creates its own pool only under `ENABLE_BACKEND_ACCESS_CONTROL="true"` (one engine per dataset, `pool_size=2`, `max_overflow=20`).
  * **Postgres graph store** — always its own pool, with leaner defaults `pool_size=2` and `max_overflow=20` (up to 22 connections). Under access control it is also created per dataset.

  So a single-user setup with `GRAPH_DATABASE_PROVIDER="postgres"` can reach roughly 40 + 22 connections at peak, and backend access control multiplies the per-dataset pools by the number of datasets.

  **Shrink the pools** to fit the server's `max_connections`. `POOL_ARGS` applies to the relational engine and is reused by the Postgres graph engine; `VECTOR_POOL_ARGS` applies to per-dataset PGVector engines. Both take a JSON object:

  ```dotenv theme={null}
  POOL_ARGS='{"pool_size": 2, "max_overflow": 4}'
  VECTOR_POOL_ARGS='{"pool_size": 2, "max_overflow": 2}'
  ```

  To minimize idle connections entirely, disable pooling so each operation opens and closes its own connection:

  ```dotenv theme={null}
  POOL_ARGS='{"poolclass": "nullpool"}'
  ```

  Alternatively, route application traffic through Neon's pooled (`-pooler`) endpoint, which supports far more concurrent clients — but disable asyncpg's prepared-statement cache when doing so (see the *asyncpg prepared-statement / connection-pooler errors* accordion above), and keep setup and migrations on the direct endpoint.
</Accordion>

<Accordion title="DatabaseNotCreatedError (Postgres)">
  For Postgres, the database named in `DB_NAME` must already exist before Cognee connects. Unlike SQLite, Cognee does **not** issue `CREATE DATABASE` for Postgres — it connects directly to `DB_NAME` and creates only the tables. If the database itself is missing, create it once with your Postgres tooling:

  ```bash theme={null}
  createdb -h 127.0.0.1 -U cognee cognee_db
  # or: psql -h 127.0.0.1 -U cognee -c "CREATE DATABASE cognee_db;"
  ```

  (The built-in Docker Postgres service from `docker compose --profile postgres up -d` already creates this database for you.)

  If you specifically see `DatabaseNotCreatedError` ("The database has not been created yet. Please call `await setup()` first."), Cognee reached Postgres but its tables (e.g. `principals`) don't exist yet. Run setup once to initialize the schema:

  ```python theme={null}
  from cognee.modules.engine.operations.setup import setup

  await setup()
  ```

  `remember()` creates the tables automatically through its underlying `add()` and `cognify()` steps, so this typically only surfaces when calling `search()` or `recall()` first on a fresh database.
</Accordion>

## When to Use Each

* **SQLite**: Local development, single-user applications, simple deployments
* **Postgres**: Production environments, multi-user applications, external hosting, co-location with pgvector
* **Turso (libSQL)**: A SQLite drop-in when you want a hosted, replicated database — the same aiosqlite driver and Alembic migrations apply unchanged, with optional embedded-replica sync against a remote Turso primary

<Columns cols={3}>
  <Card title="Vector Stores" icon="database" href="/setup-configuration/vector-stores">
    Configure vector databases for embedding storage
  </Card>

  <Card title="Graph Stores" icon="network" href="/setup-configuration/graph-stores">
    Set up graph databases for knowledge graphs
  </Card>

  <Card title="Overview" icon="settings" href="/setup-configuration/overview">
    Return to setup configuration overview
  </Card>
</Columns>
