# Hosts

Which connection string and which role to use with Supabase, RDS or a Postgres you host yourself, and what to watch for on each.

BasaltDB works with Supabase, RDS and a Postgres you host yourself, when you can log in to it as an admin. This page covers what `basalt init` needs from the host, then the details that differ on Supabase, RDS and a self-hosted server.

## What init needs from any host

- **An admin role that can create roles.** The role that owns the database, with `CREATEROLE`. A superuser is not needed.
- **A direct connection, or a pooler in session mode.** A transaction-mode pooler is not the way to connect. See [Poolers](#poolers).
- **A host your machine can reach.** The engine and the agent use the same host and port as the string you give init, so the machine that runs the engine must be able to reach it too.
- **TLS on a hosted service.** Add `?sslmode=require` to the string. A local Postgres without TLS needs nothing.
- **Special characters encoded.** If the password has `@`, `/`, `:` or `#`, URL-encode them.

The admin connection string is used once, by `basalt init` and `basalt uninstall`, typed at a hidden prompt. It is never stored. Do not export it, put it on a command line or paste it to anyone. If it ends up somewhere else, reset the admin role's password with your host.

Init copies the string you typed, keeps its host, port, database and parameters, and swaps the user and password for the agent's role and a new random password. The role is `basalt_agent_<name>`. The agent's string is written to a file that only the engine's user can read.

## Which role installs the guard

Init and `basalt uninstall` run as the admin role you give them. Init creates the `basalt_meta` schema, the guard role and the agent's role, and attaches the guard to the tables you name.

The agent's role is a plain role. Init refuses a role that has `SUPERUSER`, `CREATEROLE`, `CREATEDB`, `REPLICATION` or `BYPASSRLS`, that belongs to any role other than `basalt_agents`, or that owns anything in the database. It refuses before it changes anything.

## Row-level security

Row-level security (RLS) filters rows by policy. If a table has RLS on and no policy names the agent's role, Postgres hides every row from the agent without an error. The agent reads 0 rows from a table that has rows.

Init finds these tables before it shows the SQL. At a terminal it asks once per table:

```text
public.leads hides its rows with row-level security. Let my_bot see all 30 rows of this table? [y/N]
```

`y` adds a policy for the agent's role only. `n` skips it, and the agent will read 0 rows. In a script, `--agent-rls=all-rows` answers yes for every such table, and `--agent-rls=none` skips the question. Tables without RLS are never asked about.

If the agent already reads 0 rows, run `basalt init` again with the same flags and answer `y`. Re-running init keeps the agent's password, so the engine needs no restart. `basalt uninstall` drops the policy.

## Poolers

Every BasaltDB connection sends each statement as a single unnamed statement and sets the reason with transaction-local settings. That is what a transaction pooler needs, but transaction mode is not yet verified, so connect directly or through a session pooler.

If your host's pooler routes by user name, use a direct endpoint. Init keeps a routing suffix on the agent's user only for Supabase's pooler.

## Your host

### Supabase

In the dashboard press **Connect**. Supabase shows three strings.

| String             | Looks like                                                                                | Use it for                                                                                     |
| ------------------ | ----------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
| Direct connection  | `postgresql://postgres:[PASSWORD]@db.<ref>.supabase.co:5432/postgres`                     | Init and the agent, if your network has IPv6. The direct host is IPv6-only without the add-on. |
| Session pooler     | `postgresql://postgres.<ref>:[PASSWORD]@aws-0-<region>.pooler.supabase.com:5432/postgres` | Init and the agent otherwise. Works on IPv4 and IPv6.                                          |
| Transaction pooler | The same host, port 6543                                                                  | Not checked. Use direct or session mode.                                                       |

To check IPv6 to the direct host, run this. No secret is involved.

```sh
nc -6 -vz -w 5 db.<ref>.supabase.co 5432
```

`succeeded` means use the direct connection. A timeout or `No route to host` means use the session pooler. Add `?sslmode=require` to whichever you use, and fill in `[PASSWORD]`.

- **Role.** `postgres` is the admin. It is not a superuser and it has `CREATEROLE`, which is enough.
- **The pooler user.** On the session pooler your user is `postgres.<ref>`. The agent's user keeps the project ref, `basalt_agent_<name>.<ref>`, so the pooler can route it. Init does this for you.
- **RLS.** Tables you make in the dashboard have RLS on, and a project setting can turn it on for every new table, including tables made in the SQL editor. Expect the row-level security question, and answer `y` for tables the agent should see. See [Row-level security](#row-level-security).
- **Schemas other than `public`.** `auth` and `storage` hold your users and files. The setup summary flags them more strongly than other schemas. Give an agent tables there only on purpose.
- **A bot.** `basalt init --remote` wants the string without its password. Supabase shows it as `postgresql://postgres:[YOUR-PASSWORD]@...`. Remove the placeholder and the colon before it. See [A bot on its own computer](/guides/bot-own-computer/).
- **Default privileges.** Supabase's `postgres` role grants new tables in `public` to `anon`, `authenticated` and `service_role`. BasaltDB's own objects are made by its own role in `basalt_meta`, so none of that applies, and an agent gets no default privileges either.

### RDS

Use the instance endpoint from the RDS console, and the master user or another role that can create roles.

```text
postgresql://postgres:PASSWORD@NAME.xxxx.REGION.rds.amazonaws.com:5432/DBNAME?sslmode=require
```

- **Role.** The master user can create roles, which is all init needs. It does not have to be a superuser.
- **Reach.** The security group must let in the machine that runs `basalt init` and the machine that runs the engine. They are often two machines.
- **Use the instance endpoint.** A proxy in front of the database, such as RDS Proxy, may pool by session or by transaction and may route by user. Connect the engine to the instance endpoint, or to a session-mode pooler you control.
- **TLS.** Keep `?sslmode=require`.
- **RLS.** Postgres leaves RLS off unless you or your tooling turn it on. If a table has it on and no policy for the agent, init asks the row-level security question. See [Row-level security](#row-level-security).
- **A bot.** The bot's `--database-url` is the same endpoint without the password: `postgresql://postgres@NAME.xxxx.REGION.rds.amazonaws.com:5432/DBNAME`. See [A bot on its own computer](/guides/bot-own-computer/).

### Self-hosted

Use the host and port your Postgres listens on.

```text
postgresql://postgres:PASSWORD@HOST:5432/DBNAME?sslmode=require
```

A local Postgres without TLS needs no `sslmode`. The Docker database from the [Quickstart](/start/quickstart/) uses `postgres://postgres:demo@localhost:5432/postgres`.

- **Role.** The `postgres` superuser works. A role that owns the database and has `CREATEROLE` is enough, and is the safer choice for a database you keep.
- **Reach.** Check `listen_addresses` and `pg_hba.conf`. The machine that runs `basalt init` and the machine that runs the engine must both be allowed to connect, and so must the agent's role, `basalt_agent_<name>`, from the engine's machine.
- **PgBouncer.** Use session mode, or connect to Postgres directly.
- **RLS.** Postgres leaves RLS off unless you or your tooling turn it on. If a table has it on and no policy for the agent, init asks the question. See [Row-level security](#row-level-security).
- **The engine's machine.** The engine can run on the same server as Postgres or on another machine. As a background service, init asks for `sudo` once to create the engine's OS user.
- **A bot.** The bot's `--database-url` is the string without the password: `postgresql://postgres@HOST:5432/DBNAME`. See [A bot on its own computer](/guides/bot-own-computer/).

## Next

- [Install](/start/install/): guard your own tables.
- [Connect an agent](/start/connect-agent/): point an agent at the engine.
- [When something is refused](/guides/refused/): if the agent reads 0 rows or cannot connect.
