---
title: "Databases"
---

> Documentation Index
> Fetch the complete documentation index at: https://ermahgerd-docs.t-bd7662a5eeb4.workers.ermahgerd.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Databases

Each organization can create independent SQLite databases and bind them to
Workers using Wrangler's `d1_databases` configuration. No MySQL or PostgreSQL
server is involved. Databases are durable on the appliance's disk; destroying
the `data-02` node removes them along with the rest of that node's appliance state.

## Create, migrate, and deploy

Use the local API URL and an organization **read/write token** as described in
[Deploy Workers](/deploy-workers/). Tokens created before database permissions
were introduced retain their original permissions; create a new token rather
than expecting an existing credential to gain access silently.

```sh
examples/node_modules/.bin/wrangler d1 create my-database
```

Copy the returned ID into your Worker configuration:

```toml
[[d1_databases]]
binding = "DB"
database_name = "my-database"
database_id = "<returned-uuid>"
```

Add SQL files under the Worker's `migrations/` directory, then:

```sh
wrangler d1 migrations apply DB --remote
wrangler deploy
```

The supported CLI surface is create, list, info, delete, execute `--command`,
and migrations list/apply. Bulk import (`execute --file`), export, Time Travel,
and replicas are not implemented; they are not silently emulated. Migration
SQL and its bookkeeping execute in one transaction per migration. Do not put
explicit `BEGIN`, `COMMIT`, or savepoints inside a migration.

Inside a Worker:

```js
const session = env.DB.withSession("first-primary");
const row = await session.prepare("SELECT title FROM items WHERE id = ?")
  .bind(1).first();
const bookmark = session.getBookmark(); // synchronous, like the D1 interface
```

`prepare`, `bind`, `first`, `all`, `raw`, `run`, `batch`, `exec`, and sessions
use the pinned upstream workerd D1 JavaScript client. The synchronous client
lives inside the tenant isolate; a platform-owned capability transports queries
to the database service. The service credential and organization scope are not
application environment variables.

See [the database + assets example](https://github.com/nullbytelabs/ermahgerd/blob/main/examples/database-assets/wrangler.jsonc).
Its database ID is intentionally a placeholder, not a committed appliance ID.
For a complete appliance check that creates the example database, applies its
migration, and deploys the Worker and an assets-only demonstration site with Wrangler:

```sh
export ERMAHGERD_OWNER_PASSWORD='<local-owner-password>'
make database-assets-check
```

## Console and access

Choose **Databases** in the organization sidebar. Create a database, open its
details, copy its binding configuration, inspect lifecycle events, or run SQL
from the Query tab. The initial query lists schema objects.

Queries default to read-only and SQLite enforces that restriction, including
indirect writes. Observers cannot disable it. Members, admins, and owners can
use the write path. SQL and parameters are intentionally absent from page URLs,
metrics, logs, and spans. Database identity and the selected tab are deep-linkable.

Deleting a database is permanent and breaks existing bindings to it. The console
requires typing the database name. Back up important data outside an expendable
appliance; this release does not provide a database backup/restore product.

## Isolation, capacity, and consistency

There is one shared `ermahgerd-database` service, with a separately owned SQLite file
for each database and a separate catalog for organization ownership. Both
deployment-time binding checks and query-time lookup enforce organization
ownership. Knowing another organization's database UUID does not grant access.

SQLite authorization denies `ATTACH`, filesystem functions, extension loading,
internal platform tables, transaction control, and unsafe PRAGMAs. SQL uses
bound parameters. JSON functions and FTS5 are available. Expensive queries are
interrupted by the SQLite progress handler; bounded admission, result sizes,
and a systemd memory/CPU budget contain resource consumption.

Database allowances follow the organization tier: Free permits **3 databases**
and **128 MiB** of allocated pages, Standard permits **10** and **1 GiB**, and
Enterprise permits **25** and **4 GiB**. Each database is limited to **1 GiB**.
These are separate from R2 bucket and static asset allowances. Quota growth is
reserved before committing; a failed reservation rolls back the SQL transaction.
Startup reconciliation repairs holdings from actual database files. Existing
no-growth queries do not need a quota-service round trip after reconciliation.

Current execution limits: 100 KiB SQL, 100 statements per batch, 100 bound
parameters, 1 MiB values, 10,000 result rows and 4 MiB results per call, a
2-second SQL deadline and bounded admission waits. Organization operations are
serialized; up to eight organizations can execute concurrently. WAL and
temporary files consume additional host disk space beyond logical page quotas.

This single-node service has no replication lag or eventual-consistency mode.
Session bookmarks identify committed database sequence numbers and survive
service restarts. Worker version rollback **does not roll back database data or
schema**: use backward-compatible migrations. Database loss/restore is not
masked as a new bookmark history.

SQLite's standard Python interface does not expose rows scanned, so
`meta.rows_read` is deliberately absent rather than fabricated for billing.
`rows_written` reflects SQLite changes, not a Cloudflare billing guarantee.

## Observe and test

Ansible provisions Prometheus scraping, structured service events in Loki,
request spans in Tempo, and Grafana's **Databases & Static Assets** dashboard.
Database service duration, admission wait, commit duration, outcome counts,
and allocated pages are separate signals. Labels contain resource IDs, never SQL.

`make data-check` runs real SQLite tests, property tests, strict typing,
recursive coupling, complexity limits, and the generated binding check.
`make load-data-update` runs a controlled 10-RPS request profile while Wrangler
deploys and rolls back a Worker using both a database and assets. This is a continuity
regression check, not evidence for a four-nines database SLO. Future SLO work
must separately benchmark indexed reads, bounded writes/commits, contention,
and quota/admission errors without treating arbitrary customer SQL as platform
latency.

Source: https://ermahgerd-docs.t-bd7662a5eeb4.workers.ermahgerd.io/databases/index.mdx
