2026-02-16 20:21:15 -06:00
# Database
Paperclip uses PostgreSQL via [Drizzle ORM ](https://orm.drizzle.team/ ). There are three ways to run the database, from simplest to most production-ready.
2026-02-18 11:45:43 -06:00
## 1. Embedded PostgreSQL — zero config
2026-02-16 20:21:15 -06:00
2026-02-18 11:45:43 -06:00
If you don't set `DATABASE_URL` , the server automatically starts an embedded PostgreSQL instance and manages a local data directory.
2026-02-16 20:21:15 -06:00
```sh
pnpm dev
```
That's it. On first start the server:
2026-02-20 07:10:58 -06:00
1. Creates a `~/.paperclip/instances/default/db/` directory for storage
2026-02-18 11:45:43 -06:00
2. Ensures the `paperclip` database exists
3. Runs migrations automatically for empty databases
4. Starts serving requests
2026-02-16 20:21:15 -06:00
2026-02-20 07:10:58 -06:00
Data persists across restarts in `~/.paperclip/instances/default/db/` . To reset local dev data, delete that directory.
2026-02-16 20:21:15 -06:00
2026-03-10 15:31:05 -05:00
If you need to apply pending migrations manually, run:
```sh
pnpm db:migrate
```
When `DATABASE_URL` is unset, this command targets the current embedded PostgreSQL instance for your active Paperclip config/instance.
2026-02-18 11:45:43 -06:00
This mode is ideal for local development and one-command installs.
2026-02-16 20:21:15 -06:00
2026-02-26 10:32:33 -06:00
Docker note: the Docker quickstart image also uses embedded PostgreSQL by default. Persist `/paperclip` to keep DB state across container restarts (see `doc/DOCKER.md` ).
2026-02-16 20:21:15 -06:00
## 2. Local PostgreSQL (Docker)
For a full PostgreSQL server locally, use the included Docker Compose setup:
```sh
docker compose up -d
```
This starts PostgreSQL 17 on `localhost:5432` . Then set the connection string:
```sh
cp .env.example .env
# .env already contains:
# DATABASE_URL=postgres://paperclip:paperclip@localhost:5432/paperclip
```
Run migrations (once the migration generation issue is fixed) or use `drizzle-kit push` :
```sh
DATABASE_URL=postgres://paperclip:paperclip@localhost:5432/paperclip \
npx drizzle-kit push
```
Start the server:
```sh
pnpm dev
```
## 3. Hosted PostgreSQL (Supabase)
For production, use a hosted PostgreSQL provider. [Supabase ](https://supabase.com/ ) is a good option with a free tier.
### Setup
1. Create a project at [database.new ](https://database.new )
2. Go to **Project Settings > Database > Connection string**
3. Copy the URI and replace the password placeholder with your database password
### Connection string
Supabase offers two connection modes:
**Direct connection** (port 5432) — use for migrations and one-off scripts:
```
postgres://postgres.[PROJECT-REF]:[PASSWORD]@aws -0-[REGION].pooler.supabase.com:5432/postgres
```
**Connection pooling via Supavisor** (port 6543) — use for the application:
```
postgres://postgres.[PROJECT-REF]:[PASSWORD]@aws -0-[REGION].pooler.supabase.com:6543/postgres
```
### Configure
Set `DATABASE_URL` in your `.env` :
```sh
DATABASE_URL=postgres://postgres.[PROJECT-REF]:[PASSWORD]@aws -0-[REGION].pooler.supabase.com:6543/postgres
```
If using connection pooling (port 6543), the `postgres` client must disable prepared statements. Update `packages/db/src/client.ts` :
```ts
export function createDb(url: string) {
const sql = postgres(url, { prepare: false });
return drizzlePg(sql, { schema });
}
```
### Push the schema
```sh
# Use the direct connection (port 5432) for schema changes
DATABASE_URL=postgres://postgres.[PROJECT-REF]:[PASSWORD]@...5432/postgres \
npx drizzle-kit push
```
### Free tier limits
- 500 MB database storage
- 200 concurrent connections
- Projects pause after 1 week of inactivity
See [Supabase pricing ](https://supabase.com/pricing ) for current details.
## Switching between modes
2026-02-18 11:45:43 -06:00
The database mode is controlled by `DATABASE_URL` :
2026-02-16 20:21:15 -06:00
| `DATABASE_URL` | Mode |
|---|---|
2026-02-20 07:10:58 -06:00
| Not set | Embedded PostgreSQL (`~/.paperclip/instances/default/db/` ) |
2026-02-16 20:21:15 -06:00
| `postgres://...localhost...` | Local Docker PostgreSQL |
| `postgres://...supabase.com...` | Hosted Supabase |
Your Drizzle schema (`packages/db/src/schema/` ) stays the same regardless of mode.
2026-02-19 15:44:11 -06:00
## Secret storage
Paperclip stores secret metadata and versions in:
- `company_secrets`
- `company_secret_versions`
For local/default installs, the active provider is `local_encrypted` :
- Secret material is encrypted at rest with a local master key.
2026-02-20 07:10:58 -06:00
- Default key file: `~/.paperclip/instances/default/secrets/master.key` (auto-created if missing).
- CLI config location: `~/.paperclip/instances/default/config.json` under `secrets.localEncrypted.keyFilePath` .
2026-02-19 15:44:11 -06:00
Optional overrides:
- `PAPERCLIP_SECRETS_MASTER_KEY` (32-byte key as base64, hex, or raw 32-char string)
- `PAPERCLIP_SECRETS_MASTER_KEY_FILE` (custom key file path)
Strict mode to block new inline sensitive env values:
```sh
PAPERCLIP_SECRETS_STRICT_MODE=true
```
You can set strict mode and provider defaults via:
```sh
2026-03-03 08:45:26 -06:00
pnpm paperclipai configure --section secrets
2026-02-19 15:44:11 -06:00
```
Inline secret migration command:
```sh
pnpm secrets:migrate-inline-env --apply
```