Document secret storage in DATABASE.md and DEVELOPING.md. Update SPEC-implementation with company_secrets schema and indexes. Add migrate-inline-env-secrets script for converting existing plain env values to managed secrets (dry-run by default, --apply to commit). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
4.2 KiB
Database
Paperclip uses PostgreSQL via Drizzle ORM. There are three ways to run the database, from simplest to most production-ready.
1. Embedded PostgreSQL — zero config
If you don't set DATABASE_URL, the server automatically starts an embedded PostgreSQL instance and manages a local data directory.
pnpm dev
That's it. On first start the server:
- Creates a
./server/data/embedded-postgres/directory for storage - Ensures the
paperclipdatabase exists - Runs migrations automatically for empty databases
- Starts serving requests
Data persists across restarts in ./server/data/embedded-postgres/. To reset local dev data, delete that directory.
This mode is ideal for local development and one-command installs.
2. Local PostgreSQL (Docker)
For a full PostgreSQL server locally, use the included Docker Compose setup:
docker compose up -d
This starts PostgreSQL 17 on localhost:5432. Then set the connection string:
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:
DATABASE_URL=postgres://paperclip:paperclip@localhost:5432/paperclip \
npx drizzle-kit push
Start the server:
pnpm dev
3. Hosted PostgreSQL (Supabase)
For production, use a hosted PostgreSQL provider. Supabase is a good option with a free tier.
Setup
- Create a project at database.new
- Go to Project Settings > Database > Connection string
- 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:
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:
export function createDb(url: string) {
const sql = postgres(url, { prepare: false });
return drizzlePg(sql, { schema });
}
Push the schema
# 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 for current details.
Switching between modes
The database mode is controlled by DATABASE_URL:
DATABASE_URL |
Mode |
|---|---|
| Not set | Embedded PostgreSQL (./server/data/embedded-postgres/) |
postgres://...localhost... |
Local Docker PostgreSQL |
postgres://...supabase.com... |
Hosted Supabase |
Your Drizzle schema (packages/db/src/schema/) stays the same regardless of mode.
Secret storage
Paperclip stores secret metadata and versions in:
company_secretscompany_secret_versions
For local/default installs, the active provider is local_encrypted:
- Secret material is encrypted at rest with a local master key.
- Default key file:
./data/secrets/master.key(auto-created if missing). - CLI config location:
.paperclip/config.jsonundersecrets.localEncrypted.keyFilePath.
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:
PAPERCLIP_SECRETS_STRICT_MODE=true
You can set strict mode and provider defaults via:
pnpm paperclip configure --section secrets
Inline secret migration command:
pnpm secrets:migrate-inline-env --apply