---
title: Database
description: Every CoDuck project gets a private Postgres database. Here's how to use it.
category: database
order: 1
agent: "Each project has its own Postgres DB. DATABASE_URL is injected at runtime — it points at PgBouncer on the VPS loopback at 127.0.0.1:6432 (transaction-pool mode, max_prepared_statements=100, ?pgbouncer=true&connection_limit=1 already applied). Works for ALL stacks — Prisma, drizzle-orm, node-postgres, knex, sequelize. DIRECT_URL points at postgres directly at 127.0.0.1:5433 — used by 'prisma db push' / 'prisma migrate deploy' only, NOT the runtime request path. Read-only introspection via 'coduck db schema' / 'coduck db tables --table users'. No external connection URL exposed. Footgun: 'DROP SCHEMA public CASCADE' destroys the PgBouncer auth function (user_lookup) and breaks DATABASE_URL — don't do that. See /docs/reference/runtime for the container + networking model."
---

# Database

Every CoDuck project comes with its own private Postgres database. It's provisioned automatically on first deploy, and the connection string is injected into your container as `DATABASE_URL` (pooled) and `DIRECT_URL` (direct, for migrations).

> [!IMPORTANT]
> The database is provisioned **empty** — CoDuck creates the database, not your
> tables. You must create your schema yourself on deploy (see below). An app that
> assumes tables already exist will fail with `relation "…" does not exist`.

## How it works

On first deploy, CoDuck:

1. Creates a Postgres database dedicated to your project.
2. Provisions a dedicated database user with permissions scoped to that DB only.
3. Injects `DATABASE_URL` (PgBouncer at `127.0.0.1:6432` on the VPS loopback, transaction-pool mode with `max_prepared_statements=100`) and `DIRECT_URL` (postgres directly at `127.0.0.1:5433`, used only by Prisma's schema engine for `prisma db push` / `prisma migrate deploy`) — both reserved; you can't override them. See [Runtime architecture](/docs/reference/runtime) for the full container + networking model.

Subsequent deploys reuse the same database. Your app reads `process.env.DATABASE_URL` like any normal Postgres host — Prisma, Drizzle, Kysely, `node-postgres`, `pg`, `knex`, and `sequelize` all work against it. The [CoDuck Auth SDK](/docs/auth/overview) (`@coduckai/sdk/auth`) uses the same database to store users.

## Schema and migrations

How CoDuck applies your schema depends on whether you use Prisma and whether you've set a `preStart`:

- **Prisma, no `preStart`** — if `prisma/schema.prisma` declares `model`s, CoDuck runs
  `prisma db push` on each deploy to sync the schema (additive; it preserves tables
  it doesn't know about). This is the zero-config path.
- **You set a `preStart`** — CoDuck does **not** auto-push; your `preStart` owns
  schema setup. Use this for real migration files: `"preStart": "prisma migrate deploy"`.
- **Not using Prisma** — set a `preStart` that runs your migration tool, e.g.
  `"preStart": "drizzle-kit push"` or `"preStart": "node scripts/migrate.js"`.

`preStart` runs after install + build, before the app starts, on every deploy — see
the [`coduck.json` reference](/docs/reference/coduck-json). For migrations, prefer
`DIRECT_URL` over `DATABASE_URL` (Prisma's schema engine doesn't go through PgBouncer).

## Inspect your data

Open your project at `https://app.coduck.ai/project/<projectId>`, switch to the Cloud panel, then the **Data** tab. You'll see your schema, every table, and recent rows. The view is read-only — to write data, use your application code.

## Backups

Backups run automatically every night (cluster-level physical backups via pgBackRest, managed by CoDuck). You can also create one on demand from the Cloud panel → **Data** tab, in the Backups section. On-demand backups use `pg_dump` so you get a portable SQL export of just your project's database. Restoring is destructive and asks for confirmation before overwriting your current DB.

## Why there's no raw connection URL

> [!NOTE]
> CoDuck intentionally does not expose a raw `postgres://...` URL for external connections. The cluster is multi-tenant Postgres behind PgBouncer — a raw URL would let arbitrary external SQL bypass your application's logic and logging, can't be scoped to read-only or per-table, and can't be revoked per-session.
>
> For analytical or one-off queries, add a temporary endpoint in your app that uses its own `DATABASE_URL` — you get auth, logging, and review for free. For pulling data out of the system, use backups.

## Connection pooling

PgBouncer handles connection pooling on the server side, so a single project doesn't need a large client-side pool. Inside your app, set your client's `max_connections` to `10` or lower. Opening too many direct connections will get throttled.

## Limits

| Resource | Limit |
|---|---|
| Storage | 1 GB per project DB (soft; raisable on paid plans) |
| Connections | PgBouncer-pooled; keep app pool ≤ 10 |
| Compute | Shared with the cluster; no separate DB CPU billing |

## Common failures

- **`relation "…" does not exist` at runtime.** The database starts empty and your
  schema never got created. Make sure a Prisma schema with models is present (CoDuck
  runs `prisma db push`), or set a `preStart` that runs your migrations.
- **Migration/`db push` fails on deploy.** Check the deploy logs. A destructive change
  (dropping a column with data) is blocked by `prisma db push` without
  `--accept-data-loss`; switch to a real migration (`preStart: "prisma migrate deploy"`).
- **`bouncer config error` on every query.** PgBouncer can't authenticate connections to your DB. Almost always caused by running `DROP SCHEMA public CASCADE` (or equivalent) from your app, which destroys the `public.user_lookup()` function PgBouncer needs and the `USAGE` grant to `pgbouncer_auth`. Contact support to restore them. To migrate, drop and recreate your own tables instead of nuking `public`.
- **Connection pool exhausted.** Symptom is `too many clients` or hung requests under load. Lower `max_connections` in your client pool — PgBouncer is already pooling on the server side, so your app should open very few direct connections.
- **Schema mismatch at runtime.** Your code expects a column the DB doesn't have — a migration wasn't applied. CoDuck auto-runs `prisma generate` when your schema has models, so you don't need it in your build step; the usual cause is an uncommitted schema change.

## For developers

If you'd rather work from a terminal, the CoDuck CLI exposes read-only schema/table inspection and backup management for your project. See [CLI commands](/docs/cli/commands).

## Next

- [Deploying](/docs/projects/deploy) — when migrations run, deploy lifecycle.
- [Sending email](/docs/email/sending) — the other auto-provisioned service.
