# Use an external PostgreSQL (RDS, Supabase)

> Keep Douklar's state in Amazon RDS, Supabase or another PostgreSQL instead of the local container.

Douklar keeps its run summaries, detailed logs, audit records, sessions,
documents and image-check cache in PostgreSQL. `./install.sh local` runs PostgreSQL in Docker. For
anything beyond one machine, point it at a database you run.

| `APP_DB_PROVIDER` | Database |
|---|---|
| `local` | The PostgreSQL container from `./install.sh local` |
| `rds` | Amazon RDS for PostgreSQL |
| `supabase` | A Supabase project |
| `generic` | Any other PostgreSQL |

In local mode the installers accept only `local`, the bundled PostgreSQL. A
managed database needs remote mode.

The app uses its own schema, `douklar_app` by default. There is no migration
step to run.

## Amazon RDS

Create the RDS instance in your own account (Douklar does not create it), then
run the env wizard with the RDS profile:

```bash
./scripts/env_wizard.sh external-zitadel \
  --deployment selfhosted \
  --db-provider rds \
  --public-host app.example.com \
  --app-db-dsn 'postgresql://app_runtime:<password>@<rds-endpoint>:5432/douklar_sync?sslmode=require' \
  --zitadel-client-id '<your-client-id>' \
  --defaults --force
```

- **TLS.** With `APP_DB_PROVIDER=rds` the connection is verified against the
  RDS certificate bundle (`sslmode=verify-full`) when the DSN says nothing
  about TLS. A DSN that cannot use TLS is refused at startup.
- **Password in Secrets Manager.** Set `APP_DB_POSTGRES_SECRET_ARN` to a
  secret holding `{"username": "...", "password": "..."}`, and the DSN carries
  no password:

  ```ini
  APP_DB_POSTGRES_DSN=postgresql://app_runtime@<rds-endpoint>:5432/douklar_sync?sslmode=verify-full
  APP_DB_POSTGRES_SECRET_ARN=arn:aws:secretsmanager:us-east-1:123456789012:secret:douklar/db-AbCdEf
  ```

- **Sign-in on RDS.** The `external-zitadel` profile uses a ZITADEL you run;
  the `aws-cognito` profile uses Cognito. See
  [Sign-in with ZITADEL or Cognito](/docs/douklar/sign-in-zitadel-or-cognito).

## Supabase

In the install wizard's database step, choose Supabase:

- **Create the project for me.** Paste a Supabase access token; the installer
  creates a `douklar-…` organization and project, generates the database
  password and fills in the connection. The token is used for those setup
  calls and is not written to `.env`. Delete it in the Supabase dashboard
  afterwards.
- **Enter details manually.** Give the project ref, the pooler region label
  (for example `0-eu-central-1`) and the database password.

What Douklar requires on Supabase:

- A schema other than `public` (the default `douklar_app` works).
- TLS: `sslmode=require` or stricter.
- The session pooler on port 5432 or a direct connection. The transaction
  pooler on port 6543 is refused, because it drops the prepared statements
  the backend uses.
- A direct connection to `db.<project-ref>.supabase.co` is IPv6 only unless
  the project has the IPv4 add-on; the session pooler works over IPv4 on every
  plan.

Douklar does not use Supabase Auth; sign-in stays with ZITADEL or Cognito.

## Check the database settings

```bash
make db-postgres-preflight
```

It checks the provider, the TLS mode and the schema in the DSN before the
stack starts. Then run `make auth-check` and sign in once in a browser.

Source: https://douklar.app/docs/douklar/external-postgresql (Douklar docs, last checked 2026-10-23)
