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. 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.
examples/node_modules/.bin/wrangler d1 create my-databaseCopy the returned ID into your Worker configuration:
[[d1_databases]]
binding = "DB"
database_name = "my-database"
database_id = "<returned-uuid>"Add SQL files under the Worker’s migrations/ directory, then:
wrangler d1 migrations apply DB --remote
wrangler deployThe 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:
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 interfaceprepare, 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. 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:
export ERMAHGERD_OWNER_PASSWORD='<local-owner-password>'
make database-assets-checkConsole 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.