> For the complete documentation index, see [llms.txt](https://docs-v3.toucantoco.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs-v3.toucantoco.com/self-hosted-toucan/configuration/external-database.md).

# Configure an external database

{% hint style="info" %}
At this moment, this guide doesn't cover mTLS connections to your database. It is recommended to use mTLS connections instead of plain text connections.

We'll complete this guide as soon as mTLS connections are fully tested.
{% endhint %}

If you are hosting your database elsewhere, you can configure the Toucan Helm Stack to connect to it. However, some database migrations must be executed manually.

{% stepper %}
{% step %}

#### Disable the embedded database

Set these parameters in your values file:

{% code title="yaml: values.override.yaml" %}

```yaml
postgresql:
  enabled: false
```

{% endcode %}
{% endstep %}

{% step %}

#### Create the users and databases

Open your database client and run the following commands:

{% code title="sql: Database client" overflow="wrap" %}

```sql
CREATE USER toucan WITH PASSWORD '<password>';

-- Keycloak (Authentication Service)
CREATE DATABASE keycloak;
GRANT ALL PRIVILEGES ON DATABASE keycloak TO toucan;

-- Dataset
CREATE DATABASE dataset;
-- (optional) Create a schema for the dataset
CREATE SCHEMA dataset;
GRANT ALL PRIVILEGES ON DATABASE dataset TO toucan;

-- Layout
CREATE DATABASE layout;
CREATE SCHEMA layout;
CREATE SCHEMA workspace;
GRANT ALL PRIVILEGES ON DATABASE layout TO toucan;

-- SpiceDB
CREATE DATABASE spicedb;
GRANT ALL PRIVILEGES ON DATABASE spicedb TO toucan;

-- Vault
CREATE DATABASE vault;
GRANT ALL PRIVILEGES ON DATABASE vault TO toucan;
```

{% endcode %}
{% endstep %}

{% step %}

#### Execute manual migrations

**Vault**

Execute the migrations shown in [their documentation](https://developer.hashicorp.com/vault/docs/configuration/storage/postgresql).

{% code title="sql: Database client" overflow="wrap" %}

```sql
\c "vault";

CREATE TABLE vault_kv_store (
  parent_path TEXT COLLATE "C" NOT NULL,
  path        TEXT COLLATE "C",
  key         TEXT COLLATE "C",
  value       BYTEA,
  CONSTRAINT pkey PRIMARY KEY (path, key)
);

CREATE INDEX parent_path_idx ON vault_kv_store (parent_path);

CREATE TABLE vault_ha_locks (
  ha_key                                      TEXT COLLATE "C" NOT NULL,
  ha_identity                                 TEXT COLLATE "C" NOT NULL,
  ha_value                                    TEXT COLLATE "C",
  valid_until                                 TIMESTAMP WITH TIME ZONE NOT NULL,
  CONSTRAINT ha_key PRIMARY KEY (ha_key)
);
```

{% endcode %}
{% endstep %}

{% step %}

#### Deploy the credentials as Secret

Create the secret with:

{% code title="yaml: postgresql-secret.yaml" overflow="wrap" %}

```yaml
apiVersion: v1
kind: Secret
metadata:
  name: toucan-postgresql
  namespace: toucan
stringData:
  postgresql-password: <password>
```

{% endcode %}
{% endstep %}

{% step %}

#### Configure the database connection

For the example, we assume the database is hosted at `postgresql.example.com`, with the default port `5432`, set these parameters in your values file:

{% code title="yaml: values.override.yaml" %}

```yaml
layout:
  config:
    database_postgres:
      url: 'postgresql://toucan:[[ getenv "LAYOUT_POSTGRESQL_PASSWORD" ]]@postgresql.example.com:5432/layout?schema=layout'
      url_admin: 'postgresql://toucan:[[ getenv "LAYOUT_POSTGRESQL_PASSWORD" ]]@postgresql.example.com:5432/layout?schema=layout'
    specific:
      workspace_database_postgres:
        url: 'postgresql://toucan:[[ getenv "LAYOUT_POSTGRESQL_PASSWORD" ]]@postgresql.example.com:5432/layout?schema=workspace'
        url_admin: 'postgresql://toucan:[[ getenv "LAYOUT_POSTGRESQL_PASSWORD" ]]@postgresql.example.com:5432/layout?schema=workspace'
  initconfig:
    secrets:
      LAYOUT_POSTGRESQL_PASSWORD:
        name: toucan-postgresql
        key: postgresql-password

dataset:
  config:
    schema: dataset
    url: 'postgresql://toucan:[[ getenv "DATASET_POSTGRESQL_PASSWORD" ]]@postgresql.example.com:5432/dataset'
    url_admin: 'postgresql://toucan:[[ getenv "DATASET_POSTGRESQL_PASSWORD" ]]@postgresql.example.com:5432/dataset'
  initconfig:
    secrets:
      DATASET_POSTGRESQL_PASSWORD:
        name: toucan-postgresql
        key: postgresql-password

spicedb:
  config:
    datastore:
      uri: 'postgresql://toucan:$(PG_PASSWORD)@postgresql.example.com:5432/spicedb'

  # For the init container to run migrations
  migration:
    extraEnvVars:
      - name: PG_PASSWORD
        valueFrom:
          secretKeyRef:
            name: toucan-postgresql
            key: postgresql-password

  # For the main container
  extraEnvVars:
    - name: PG_PASSWORD
      valueFrom:
        secretKeyRef:
          name: toucan-postgresql
          key: postgresql-password

vault:
  toucanEnvVars:
    - name: PG_PASSWORD
      valueFrom:
        secretKeyRef:
          name: 'toucan-postgresql'
          key: 'postgresql-password'

    - name: VAULT_PG_CONNECTION_URL
      value: 'postgresql://toucan:$(PG_PASSWORD)@postgresql.example.com:5432/vault'

    - name: TOUCAN_RO_SECRET_KEY
      valueFrom:
        secretKeyRef:
          name: '{{- include "toucan-stack.s3.keys.toucan_ro.secretName" . -}}'
          key: '{{ .Values.global.s3.keys.toucan_ro.existingSecret.key }}'

    - name: TOUCAN_RW_SECRET_KEY
      valueFrom:
        secretKeyRef:
          name: '{{- include "toucan-stack.s3.keys.toucan.secretName" . -}}'
          key: '{{ .Values.global.s3.keys.toucan.existingSecret.key }}'

keycloak:
  config:
    dataSource:
      host: 'postgresql.example.com'
      port: '5432'
      database: 'keycloak'
      schema: 'public'
      user: toucan

      password:
        secretName: toucan-postgresql
        secretKey: postgresql-password
```

{% endcode %}
{% endstep %}

{% step %}

#### Upgrade Toucan Stack

{% code title="shell: /work/" overflow="wrap" %}

```shell
helm upgrade --install toucan-stack oci://quay.io/toucantoco/charts/toucan-stack \
  --namespace toucan \
  --values ./values.override.yaml
```

{% endcode %}
{% endstep %}
{% endstepper %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs-v3.toucantoco.com/self-hosted-toucan/configuration/external-database.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
