1. guides
prorm
  • prorm documentation
  • Known test gaps
  • Architecture
  • Core concepts
  • Database types & coverage
  • Getting started
  • Going further
  • compliance
    • Compliance
    • The security toolkit: `prorm audit`
    • Checklists by regulation
    • Declaring personal data
    • Evidence bundles
    • Evidence
    • Frameworks: `@Security`
    • Jurisdictions
    • Platform policies
    • Protecting data
    • The regulation dataset
    • Residency and cross-border transfers
    • Retention & integrity
    • Status
    • Subject rights
  • decorators
    • prorm with decorators
    • Models with decorators
    • Querying a decorated model
    • Relations with decorators
  • diagrams
    • Diagram generators
    • Chen diagram
    • Class diagram
    • Dependency diagram
    • ER diagram
    • Flow diagram
    • From your models to a diagram
    • Gantt diagram
    • Index diagram
    • Migration diagram
    • Model diagram
    • Package diagram
    • Crow’s-foot diagram
    • Schema documentation
    • Sequence diagram
    • State diagram
    • Tree diagram
  • dialects
    • SQL dialects
    • AlloyDB Dialect
    • AlloyDB Omni Dialect
    • Amazon Athena Dialect
    • Aurora DSQL Dialect
    • Amazon Aurora MySQL Dialect
    • Amazon Aurora PostgreSQL Dialect
    • Azure Database for MySQL Dialect
    • Azure Database for PostgreSQL Dialect
    • Azure SQL Database Dialect
    • Azure SQL Managed Instance Dialect
    • Google BigQuery Dialect
    • Citus Dialect
    • ClickHouse dialect
    • Cloud Authentication (`cloud-auth`)
    • Cloud SQL for SQL Server Dialect
    • Cloud SQL for MySQL Dialect
    • Cloud SQL for PostgreSQL Dialect
    • CockroachDB Dialect
    • Azure Cosmos DB for PostgreSQL Dialect
    • CrateDB Dialect
    • Cloudflare D1 Dialect
    • Databricks dialect
    • IBM Db2 Dialect
    • Dolt Dialect
    • Apache Doris Dialect
    • Druid Dialect
    • DuckDB Dialect
    • Exasol dialect
    • Microsoft Fabric Data Warehouse Dialect
    • Firebird Dialect
    • Greenplum Dialect
    • SAP HANA Dialect
    • MySQL HeatWave Dialect
    • Azure Data Explorer (Kusto) Dialect — T-SQL
    • MariaDB Dialect
    • Materialize Dialect
    • MonetDB Dialect
    • MotherDuck Dialect
    • Microsoft SQL Server Dialect
    • MySQL Dialect
    • Neon Dialect
    • OceanBase Dialect
    • openGauss Dialect
    • Oracle Dialect
    • Oracle Autonomous Database Dialect
    • ParadeDB Dialect
    • PolarDB Dialect
    • PostgreSQL Dialect
    • Presto Dialect
    • QuestDB Dialect
    • Aurora RDS Data API Dialect (`rdsdata`)
    • Amazon RDS for Db2 Dialect
    • Amazon RDS for MariaDB Dialect
    • Amazon RDS for SQL Server Dialect
    • Amazon RDS for MySQL Dialect
    • Amazon RDS for Oracle Dialect
    • Amazon RDS for PostgreSQL Dialect
    • Amazon Redshift
    • Amazon Redshift Data API Dialect
    • RisingWave Dialect
    • rqlite Dialect
    • SingleStore Dialect
    • Snowflake Dialect
    • Google Cloud Spanner Dialect
    • Spanner (PostgreSQL interface) Dialect
    • SQLite Dialect
    • StarRocks Dialect
    • Supabase Dialect
    • Azure Synapse Analytics (dedicated SQL pool)
    • Teradata Dialect
    • TiDB Dialect
    • TimescaleDB Dialect
    • Trino Dialect
    • Turso / libSQL Dialect
    • Vertica dialect
    • Vitess Dialect
    • Yellowbrick Dialect
    • YugabyteDB Dialect
  • guides
    • Access rules and behaviour profiles
    • ArangoDB (AQL)
    • Associations
    • Audit levels and escalation
    • Audit logging
    • In the browser
    • Bulk operations
    • Caching
    • Capabilities, tiers and team roles
    • CLI
    • Cloudflare
    • Compliance & data protection
    • Connection Pooling & Performance
    • Security in the console
    • Couchbase: structured N1QL queries
    • Data types
    • Decorators
    • Eager loading
    • Enums and constants
    • Error handling & error types
    • Extensions
    • External fields
    • Foreign data wrappers
    • Graph databases
    • Hooks & lifecycle events
    • Indexes & constraints
    • InfluxDB: structured Flux pipelines
    • klock-errors
    • klock — the `.klock` file
    • Local databases (`prorm db up`)
    • Structured log search — Splunk, Graylog, Papertrail, Sumo Logic
    • Logging
    • Grafana Loki: structured LogQL queries
    • MCP server
    • Migrate — the data transfer subsystem
    • Migrations & Schema Sync
    • Models from an existing database
    • Defining models
    • Multiple databases
    • OrientDB, Neptune and TigerGraph
    • Passwords
    • Importing a Prisma schema
    • Structured PromQL and MetricsQL
    • Query firewall
    • QueryInterface
    • Query operators reference
    • Query optimization
    • Composing find options with `pipe()` and `Q`
    • Query safety: the fail-closed WHERE guard
    • Querying
    • Raw Queries & Query Building
    • Read replicas
    • Schema diffing & migration generation
    • Views, triggers & other schema objects
    • Scopes & default scopes
    • Seeders
    • SPARQL / RDF triplestores
    • SQL constants
    • SQL features
    • SQL function builders
    • SQLite advanced features
    • StoreKey — a prod-orm database as the storage
    • Streaming results
    • Terraform
    • Transactions & Isolation Levels
    • Type mapping: legacy and modern
    • TypeScript types
    • Database users, roles & privileges
    • Validation & constraints
    • Virtual & Computed Fields
    • Writing docs
  • javascript
    • prorm in JavaScript
    • Models in JavaScript
    • Querying in JavaScript
    • Relations in JavaScript
  • runbooks
    • Operational runbooks
    • Connection failures
    • Credential rotation
    • Failover
    • Incident checklist
    • Deploying klock v2
    • Migration rollback
  • running-databases
    • Running databases in Docker
    • Running prorm under Bun in Docker
    • Running Cassandra in Docker with prorm
    • Running ClickHouse in Docker with prorm
    • Running CockroachDB in Docker with prorm
    • Running prorm under Deno in Docker
    • Running Elasticsearch in Docker with prorm
    • Running MariaDB in Docker with prorm
    • Running MongoDB in Docker with prorm
  • prorm control plane
    • Introduction
    • Authentication
    • Errors
    • Getting started
      • Service
        • Is it up
        • This description
    • Account
      • Sign-up and verification
        • Create an account
        • Exchange credentials for a token
        • Verify with the emailed code
        • Verify by clicking the emailed link
        • Name the parent or guardian
        • What a consent link is about
        • Give consent for a child's account
        • Ask the parent or guardian again
        • Send a new code and link
      • Family
        • Everyone this account is responsible for
        • One child, in detail
        • Turn a limit on or off
      • Profile
        • Who am I
        • Change the account
        • Every address on the account
        • Add an address
        • Confirm an address with the code mailed to it
        • Mail a fresh code to an unconfirmed address
        • Make an address primary
        • Remove an address
        • What this caller may do
    • Security
      • Sessions
        • Everywhere this account is signed in
        • Sign out everywhere else
        • Sign one out
        • A token for the CLI on a named machine
        • A token for the customer console, from the staff one
        • Park a sign-in for the CLI to collect
        • The CLI collecting its sign-in
        • The team's members' live sessions
        • Sign a member's session out
        • Sign an account out everywhere
        • Everywhere the account is signed in
        • Sign an account's session out
      • Single sign-on
        • Begin a single sign-on
        • The provider's return
        • Link the signed-in account to a team's provider
        • Which team an address signs in to
        • The team's single sign-on, as a member sees it
        • Set the team's OpenID Connect provider up, or change it
        • Remove the provider
        • The team's domain claims and the DNS record each needs
        • Prove a claimed domain
      • MFA and passwords
        • Start enrolling a second factor
        • Prove the app has it
        • Remove the second factor
        • Ask for a reset link
        • Set a new password with the emailed token
      • Klock
        • Open a device-bound klock session
        • Check in, for the next grant
        • Step up the session asking
        • Rotate the device key
        • Freeze a session
        • Unfreeze a session
        • A guest grant
        • The revocation feed
        • The published keyset
        • What this control plane supports
      • Devices
        • Your devices, or a team's
        • Approve a new device's pairing code
        • One device
        • Rename a device
        • Revoke a device
        • Suspend a device
        • Reinstate a suspended device
        • Wipe a device
        • Ask a device to rotate its key
        • The team's members' devices
        • Revoke a member's device
        • An account's devices
        • Revoke an account's device
        • Wipe an account's device
        • The device fleet
      • Access rules and behaviour
        • The access rules that bear on you
        • Add an access rule
        • This request, as the rules see it
        • One access rule
        • Remove an access rule
        • The behaviour profiles that bear on you
        • Add or change a behaviour profile
        • The profile presets
        • Your behaviour findings
        • Acknowledge a finding
        • One behaviour profile
        • Remove a behaviour profile
        • The team's access rules
        • Add a team access rule
        • Remove a team access rule
        • The team's behaviour profiles
        • Add or change a team behaviour profile
        • Remove a team behaviour profile
        • What the team's profiles caught
        • Acknowledge a team finding
      • Recovery and lockdown
        • Ask for a recovery link
        • Recover access with the emailed code
        • Is the account locked down
        • Lock the account down
        • Release a lockdown
        • Lock a member down
        • Release a member's lockdown
      • MCP tokens
        • Make a delegated token for one MCP client
        • The account's delegated tokens
        • Revoke a delegated token
        • What a delegated token may do now
        • Run one MCP tool with a delegated token
        • Run a device-signed MCP tool call relayed by the MCP Worker
      • Kerberos
        • Give the team its own Kerberos realm
        • The team's realm
        • Remove the team's realm
    • Logging and monitoring
      • Audit log
        • What has been done to the account
        • What has happened to this account
        • The account's audit trail
      • Operations
        • Everything that has been asked of this database
        • One, with its steps
        • Stop it
        • Put a failed one back in the queue
        • Every operation, across all accounts
      • Metrics and events
        • Every measurement, for a graph
        • Who did what to it
    • Billing
      • Plans and pricing
        • What the plans allow
        • The rate card
        • What one database would cost
        • What this account is paying for, itemised
      • Subscriptions and invoices
        • What this month's invoice will say
        • What this account is on
        • What it has been charged
        • Start a Stripe Checkout session
        • Open the customer portal
        • Stripe's events
        • Buy more connection pools
      • Team billing
        • The team's billing account
        • What the team has been charged
        • What this month's invoice will say
        • The team's cards
        • Add a card, through a Stripe-hosted setup session
        • Make a card the team's default
        • Remove a card
        • Start the team paying
        • Open the Stripe portal on the team's customer
        • Change the team's plan
        • Who receives the team's bills
        • How the team pays for transfers
    • Support
      • Support chat
        • My conversations
        • Start a conversation
        • Every support conversation, open first
        • A ticket for the socket
        • One conversation, with who is in it and who is connected
        • Rename a group; open or close a support conversation
        • The lines, oldest first
        • Say something
        • Add somebody
        • Mark read
    • Administration
      • Users
        • Every account
        • One account, with everything hanging off it
        • Grant or revoke admin
        • Verify an account by hand
        • Lock or unlock sign-in
        • Allow or hold provisioning
        • Send the verification mail again
        • Set the plan by hand
      • Platform
        • Every database and store, across all accounts
        • The counts worth putting on a dashboard
      • Staff and permissions
        • What kind of admin the caller is, and which scopes they hold
        • The admin presets
        • Every admin, and the scopes each resolves to
        • Make an account an admin of a kind
        • Change an admin's kind, or a custom admin's scopes
        • Revoke admin
      • Team administration
        • Every team, with its owner and size
        • One team in full — members with their accounts, and projects
        • Hand the team to another member
        • Put an account in the team, active at once
        • Change a member's role
        • Remove a member
      • Assumed identities
        • Assume an identity — act as a customer account
        • The identities the caller holds open
        • End an assumed identity
      • Support inbox
        • The platform's addresses
        • Add a platform address
        • One address, with its counts
        • Change an address's label, routing, or whether it accepts mail
        • Remove an address that holds no mail
        • Make, adopt or repair the address's routing rule
        • Every admin's personal address, made where missing
        • File a raw message, as if it had arrived
        • The inbox
        • One message, whole
        • Mark a message
        • Hand a message to an admin, or take it back
        • Leave an internal note
        • Reply, from the address the message came to
        • What is waiting
    • Databases
      • Databases
        • Data stores
          • Every data store on the account
          • Provision a data store
          • Change a data store that is already running
          • One data store
          • Deprovision it
          • Report what it is using
          • How to reach a data store
        • Connections and credentials
          • The api keys on a database
          • Issue a key
          • Revoke a key
          • The engine users we created
          • Create a user inside the database
          • Give a user a new password
          • Drop a user
          • How the database is reached
          • Set how it is reached
          • Revoke every api key on it
          • How to connect to it
          • The ssh keys that open its bastion
          • Add a key, or have one made
          • The private half of a generated key, once
          • Revoke an ssh key
        • Schema and objects
          • The SQL objects we put in it
          • Create one, and apply it
          • What is in the database, as the engine reports it
          • One table
          • What it would run, without running it
          • Redefine an object in place
          • Drop it
          • The extensions installed in it
        • Migrations
          • A plan compiled to statements, recording nothing
          • The migrations recorded for it
          • Record one
          • Apply everything recorded and not yet run
          • Apply everything pending, oldest version first
        • Lifecycle
          • Grow or shrink it
          • Stop it accepting connections
          • Start it again
          • Move it to another region
        • Backups and replicas
          • Take a backup
          • The backups it has
          • Restore one into it
          • What has been restored into it, and from where
          • Its read copies
          • Add one
        • Policies
          • The catalog
          • One policy
          • What this database is held to
          • Switch a policy on, or change its knobs
          • Switch it off
          • Mark the policies reviewed
          • Evaluate the periodic policies now
          • Who is behind a credential
        • Passwordless
          • The database's passwordless sessions
          • Issue a passwordless session (v1)
          • Revoke a passwordless session
          • Bind a session to a device key
          • Issue a v2 session for a host to bind
          • Redeem an enrolment ticket on the host
          • Check a passwordless credential
          • Run one described query under a passwordless credential
        • Database roles
          • The database's roles
          • Define a role
          • One role
          • Change what a role permits
          • Delete a role, and revoke every key granted it
          • Grant a role, which mints a key
          • Who holds a role
          • The api keys bound to a role
        • Pools
          • A database's connection pools, and how busy each is
          • Open another pool
          • Rename a pool or change its size
          • Close a pool
        • Data plane
          • Run a query against a database
          • Several statements, in order, over one connection
          • Is this database answering
          • What the proxy itself is holding
        • Transfers
          • The strategies this account's tier may use
          • Price a transfer without creating it
          • The account's transfer jobs, newest first
          • Create a transfer job
          • One job, and whether its certificate holds
          • Cancel an unfinished job
          • Start a created job, confirming its price
          • Report a job's status (the runner, or the owner's side of a local run)
        • Every database on the account
        • Provision a database
        • Change a database that is already running
        • One database
        • Deprovision it
        • Read or write it
        • Report what it is using
        • The engines this API will provision
        • Where a managed database can be put
    • Teams
      • Teams
        • Members
          • Who is on it
          • Invite somebody
          • Accept an invitation
          • Change somebody's role
          • Remove somebody
        • Projects
          • The projects a team runs
          • Start one
          • Rename a project, or move its environment
          • Delete a project
          • One project, and what is in it
          • Put a database or store in it
          • What is in a project
          • Take one out
        • The teams this account is on
        • Start one
        • The twelve roles, and what each may do
        • Rename a team
        • Close a team
        • One team
        • Hand the team to somebody else
        • Leave a team
    • Schemas
      • Error
      • ValidationErrors
      • Token
      • PolicyCategory
      • PolicySeverity
      • PolicyAction
      • PolicyTrigger
      • PolicyParam
      • Policy
      • PolicyCatalog
      • PolicyAssignment
      • PolicyOutcome
      • PolicyFiring
      • CredentialTrace
      • Relation
      • Restrictions
      • ChildSummary
      • SignupRequest
      • SigninRequest
      • UpdateMeRequest
      • User
      • Address
      • GeoPoint
      • D1Region
      • Usage
      • UsageReport
      • Resource
      • Database
      • DataStore
      • NewDatabase
      • NewDataStore
      • QueryRequest
      • DatabasePrivilege
      • ApiKey
      • DbUser
      • Access
      • AccessRequest
      • DatabaseObject
      • NewDatabaseObject
      • NewMigration
      • SchemaOperation
      • Migration
      • Operation
      • Session
      • AdminPowers
      • Admin
      • AuditEntry
      • TeamRole
      • Conversation
      • ChatMessage
      • TeamSso
      • Team
      • TeamMember
      • SshKey
      • ConnectionInfo
      • InboxAddress
      • InboxMailbox
      • InboxMessageSummary
      • InboxMessage
      • InboxNote
      • Page
      • Quote
      • Plan
      • ApiError
      • CapabilityRefusal
      • KlockErrorCode
      • PageWindow
      • SessionView
      • KlockStepUpRequest
      • KlockOpenRequest
      • KlockOpenedSession
      • UpdateMeResult
      • CapabilitiesView
      • SessionLimits
      • Device
      • DeviceListed
      • DeviceTeamRef
      • DeviceCommand
      • DeviceAuditLine
      • DeviceActionRequest
      • StaffActionRequest
      • ManagedSession
      • SessionRevoked
      • AccessRule
      • AccessRuleRequest
      • BehaviourProfile
      • BehaviourProfileRequest
      • BehaviourProfileSaved
      • BehaviourFinding
      • BehaviourFindingAck
      • StepUpProof
      • Removed
      • MemberLockdownRequest
      • McpClient
      • McpDelegatedView
      • McpToolCall
      • McpForwardRequest
      • PasswordlessScope
      • RoleScope
      • PasswordlessSession
      • PasswordlessTlsMaterial
      • PasswordlessKerberosMaterial
      • ServiceDeviceKeys
      • ServiceSession
      • DatabaseRole
      • TransferEndpoint
      • TransferJobRequest
      • TransferQuote
      • TransferStrategy
      • TransferJob
      • TransferJobAccess
      • Pool
      • PoolAllowance
      • BillingSubscription
      • Invoice
      • BillingContact
      • PaymentMethod
      • UpcomingCharges
      • TeamBilling
      • TeamKerberosRealm
      • TeamSsoDomain
      • TeamSsoResponse
      • StoreConnection
      • BatchFailure
      • ProxyStatus
  1. guides

Local databases (`prorm db up`)

prorm db up runs a database on your machine in Docker or Podman: it picks an
image (one that ships the extensions you ask for, or a small generated
Dockerfile), writes a docker-compose.yml and a git-ignored .env of
generated credentials, starts the container, waits for its health check, and
installs the extensions.
$ prorm db up --dialect postgres --ext vector,postgis
$ prorm db url
postgres://prorm:****@localhost:5432/postgres
$ prorm db status
$ prorm db down                    # keeps the data; --volumes deletes it
prorm db init --docker still scaffolds a bare compose file with a local
klock; db up is the managed path. See db init and the local
klock
below.

Commands#

CommandDoes
db up [name] [--dialect] [--ext] [--name] [--port] [--version] [--image]Write the files, start the container, install extensions. --no-start only writes; --dry-run prints the files (secrets masked) and the commands; --print-run also prints a single docker run; --force overwrites a compose file you edited.
db down [name] [--volumes]compose down; --volumes deletes the data (asks, or --yes).
db status [name]Engine, image, port, container health and the redacted URL, for one or all.
db url [name] [--show-secret]The connection URL — password masked unless --show-secret.
Every command takes --dir (default docker/local-db) and, where it runs
anything, --runtime docker|podman. The name defaults to the dialect for
db up, and to the only local database for the others.
Each database gets its own directory:
docker/
  local-db/
    postgres/
      docker-compose.yml               # no secrets in it
      .env                             # 0600, git-ignored
      Dockerfile                       # only when an extension has to be added
      initdb/
        10-prorm-extensions.sql        # PostgreSQL, when extensions were asked for
    mysql/                             # a second database gets its own directory
      docker-compose.yml
      .env
.gitignore                             # gains docker/local-db/*/.env

From nothing to a migrated database#

The whole loop, for a project that keeps its migrations in migrations/ (see
Migrations). The migration commands read DATABASE_URL,
which is exactly what db url --show-secret prints.

Write the files first#

--no-start writes everything and starts nothing, so you can read what it made
before any container exists:
$ prorm db up --dialect postgres --ext vector --no-start
✓ Wrote ./docker/local-db/postgres/docker-compose.yml (pgvector/pgvector:0.7.4-pg16).
Credentials: ./docker/local-db/postgres/.env (0600; added docker/local-db/*/.env to .gitignore).
Start it with: prorm db up --name postgres   (or: docker compose -f ./docker/local-db/postgres/docker-compose.yml up -d)
$ prorm db status
name      dialect   image                         port  state        url
postgres  postgres  pgvector/pgvector:0.7.4-pg16  5432  not running  postgres://prorm:****@localhost:5432/postgres

Start it#

A second db up reuses the .env it wrote, so the credentials, and later the
data volume, stay the same:
$ prorm db up --name postgres
✓ postgres is up on 127.0.0.1:5432 (docker).
Extensions installed: vector
URL: postgres://prorm:****@localhost:5432/postgres
The full URL: prorm db url --name postgres --show-secret   (or DATABASE_URL in ./docker/local-db/postgres/.env)

Point the project at it#

$ export DATABASE_URL=$(prorm db url --show-secret)
$ prorm migration:run
$ prorm db:seed
In application code the same variable is the whole configuration:
import { Prorm } from 'ts-prorm-orm';

export const prorm = new Prorm({ dialect: 'postgres', url: process.env.DATABASE_URL });
await prorm.connect();

Stop it, or throw it away#

$ prorm db down                     # container gone, volume kept
$ prorm db down --volumes --yes     # container and data gone
[!CAUTION]
db down --volumes deletes the named volume that holds the data. It asks
first unless you pass --yes, and there is no undo: the next db up starts
an empty database with the same credentials.

What the files look like#

db up --dry-run prints every file it would write, with the secrets masked, and
the commands it would run. This is its output for
prorm db up --dialect postgres --ext vector,pg_trgm --dry-run:
# Generated by `prorm db up` for the local postgres database "postgres".
# Local credentials: git-ignored, never commit. `prorm db url` prints the URL (redacted).
PRORM_DB_DIALECT=postgres
PRORM_DB_IMAGE=pgvector/pgvector:0.7.4-pg16
PRORM_DB_SERVICE=postgres
PRORM_DB_CONTAINER=prorm-postgres
PRORM_DB_PORT=5432
PRORM_DB_USER=prorm
PRORM_DB_PASSWORD=****
PRORM_DB_ROOT_PASSWORD=****
PRORM_DB_NAME=postgres
DATABASE_URL=postgres://prorm:****@localhost:5432/postgres
# Read by the image:
POSTGRES_USER=prorm
POSTGRES_PASSWORD=****
POSTGRES_DB=postgres
# ── would run ──
docker compose -f ./docker/local-db/postgres/docker-compose.yml up -d --wait
docker compose -f ./docker/local-db/postgres/docker-compose.yml exec -T postgres psql -v ON_ERROR_STOP=1 -U prorm -d postgres -c 'CREATE EXTENSION IF NOT EXISTS "vector"'
docker compose -f ./docker/local-db/postgres/docker-compose.yml exec -T postgres psql -v ON_ERROR_STOP=1 -U prorm -d postgres -c 'CREATE EXTENSION IF NOT EXISTS "pg_trgm"'

Per engine#

The engine decides the health check, the URL's user and what runs after start:
$ prorm db up --dialect postgres
$ prorm db url
postgres://prorm:****@localhost:5432/postgres
Health check pg_isready; extensions run through psql as the app user.
$ prorm db up --dialect mysql
$ prorm db url
mysql://prorm:****@localhost:3306/mysql
Health check mysqladmin ping with the root password read from the container's
environment; --ext plugins run through INSTALL PLUGIN, and one that is
already installed on a re-run is not an error.
$ prorm db up --dialect mssql
$ prorm db url
mssql://sa:****@localhost:1433/mssql
The image has no "create a database" variable, so after start db up runs
sqlcmd … -Q "IF DB_ID(N'mssql') IS NULL CREATE DATABASE [mssql]" inside the
container. The URL user is sa.
Several at once is just several names, each on its own port:
$ prorm db up --dialect postgres
$ prorm db up shop --dialect mysql --port 3307
$ prorm db status
$ export DATABASE_URL=$(prorm db url shop --show-secret)

Images#

Tags are pinned so two people who run db up a month apart get the same
server. Where docker-compose.test.yml already chose an image, the same one
is used, pinned one step further.
--imageDialectImageShips (beyond the engine)
postgres (default)postgrespostgres:16.4-alpinecontrib: pg_trgm, pgcrypto, uuid-ossp, hstore, citext, ltree, btree_gist, pg_stat_statements, postgres_fdw, …
postgres-debianpostgrespostgres:16.4-bookwormcontrib; the Dockerfile base
pgvectorpostgrespgvector/pgvector:0.7.4-pg16contrib + vector
postgispostgrespostgis/postgis:16-3.4contrib + postgis, postgis_raster, postgis_topology, postgis_tiger_geocoder, postgis_sfcgal, address_standardizer
timescaledbpostgres, timescaledbtimescale/timescaledb:2.16.1-pg16contrib + timescaledb
timescaledb-hapostgres, timescaledbtimescale/timescaledb-ha:pg16.4-ts2.16.1contrib + timescaledb, timescaledb_toolkit, vector, vectorscale, PostGIS, pg_cron, pgaudit, pgrouting, hypopg, pg_repack
mysqlmysqlmysql:8.0.39plugins/components: validate_password, component_validate_password, connection_control, semi-sync, group_replication, clone, keyring_file, rewriter, ddl_rewriter, version_tokens
mariadbmariadbmariadb:11.4.3plugins: server_audit, simple_password_check, disks, metadata_lock_info, query_response_time, locales, semi-sync
mssqlmssqlmcr.microsoft.com/mssql/server:2022-CU14-ubuntu-22.04— (Developer edition; x86-64, Rosetta on Apple Silicon)
azure-sql-edgemssqlmcr.microsoft.com/azure-sql-edge:1.0.7— (arm64; retired by Microsoft in 2025, connects to master)
oracleoraclegvenzl/oracle-free:23.5-slim— (app user in FREEPDB1)
clickhouseclickhouseclickhouse/clickhouse-server:24.8.4.13-alpine—
cockroachdbcockroachdbcockroachdb/cockroach:v23.2.13the no-op shims (pg_trgm, hstore, uuid-ossp, …); runs --insecure
yugabytedbyugabytedbyugabytedb/yugabyte:2.20.1.0-b97contrib + pg_hint_plan, pgaudit, orafce, pg_cron
tidbtidbpingcap/tidb:v8.1.1— (standalone, passwordless root)
cratedbcratedbcrate:5.8.3—
questdbquestdbquestdb/questdb:8.1.1—
risingwaverisingwaverisingwavelabs/risingwave:v2.0.1— (single node)
db2db2icr.io/db2_community/db2:11.5.9.0— (privileged, several GB RAM)
SQLite and DuckDB are embedded — db up refuses them and points at
prorm db init.
--version replaces the pinned tag with any tag you give (validated as a tag,
not interpolated into a shell). For a generated Dockerfile the PostgreSQL major
version in the tag (17-3.5, pg17) picks the package names.

How extensions choose the image#

For --ext, the first image in the dialect's list that ships every requested
extension wins (pgvector → pgvector/pgvector, postgis → postgis/postgis,
timescaledb,vector → timescaledb-ha). The TimescaleDB images are only
chosen when TimescaleDB is asked for.
When no image ships them all, a two-line Dockerfile is generated FROM the
Debian image that ships the most (postgis/postgis, pgvector/pgvector or
postgres:16.4-bookworm) and installs the rest from apt.postgresql.org, which
those images already use:
The packages db up knows: vector, the PostGIS family, pgrouting,
pg_cron, pg_partman, hypopg, pgaudit, pg_repack, pg_hint_plan,
pg_squeeze, pg_bigm, orafce, plv8, pgtap, pg_wait_sampling,
pglogical, h3, ip4r, prefix, rum, semver, periods. Anything else
is refused with what each image ships.
Extensions that must be preloaded (pg_cron, pgaudit, pg_stat_statements,
pg_squeeze, pg_wait_sampling, pglogical) start PostgreSQL with
-c shared_preload_libraries=… (keeping timescaledb on a Timescale image);
pg_cron also gets cron.database_name. In the compose file that request
becomes a built image and a command:
After the health check, each extension is installed inside the container —
psql -c 'CREATE EXTENSION IF NOT EXISTS "vector"' over the trusted local
socket, INSTALL PLUGIN through the mysql client on MySQL/MariaDB (a plugin
that is already installed on a re-run is fine). PostgreSQL images also get an
initdb/ script with the same statements, so a plain docker compose up
installs them on first initialisation. On SQL Server, the application database
is created the same way.

Credentials#

Generated per database: 24 random letters and digits (upper, lower and a
digit — SQL Server's complexity rule — and nothing a URL, shell or Oracle
IDENTIFIED BY treats specially).
Written only to .env beside the compose file, mode 0600. The compose file
reads it with env_file:; health checks read $$POSTGRES_PASSWORD from the
container's environment. The compose file, the docker run line and every
process argument are free of secrets; the MySQL/SQL Server clients read the
root password from the container's own environment.
docker/local-db/*/.env (relative to --dir) is added to .gitignore.
Printed only redacted — postgres://prorm:****@localhost:5432/app — through
the same redactSecrets the rest of the CLI uses. db url --show-secret
prints the full URL for piping:
export DATABASE_URL=$(prorm db url --show-secret).
A second db up reuses the .env, so the existing data volume still opens.
Ports are bound to 127.0.0.1, not every interface.

Docker or Podman#

db up looks for docker with Compose v2 (docker compose), then
docker-compose, then podman compose, then podman-compose;
--runtime or $PRORM_CONTAINER_RUNTIME chooses. With neither, the files are
still written and the command prints how to install one for your platform
(Docker Desktop; brew install podman podman-compose && podman machine init && podman machine start; winget install RedHat.Podman; your package manager). A
runtime whose daemon is not running is reported as such. Front ends without
up --wait are followed by polling the container's health.
Every process is spawned with an argument array — never through a shell — so
a path with spaces is one argument, not a command line.

db init and the local klock#

prorm db init makes a database for local work and writes a local klock
beside it: a version 2 .klock of kind: "local", mode 0600, holding a
fresh 32-byte secret and how to reach the database.
$ prorm db init app                                   # SQLite ./app.db, and ./.klock
$ prorm db init shop --docker --dialect postgres      # a docker-compose.yml, and ./.klock with the URL
$ prorm db init --url postgres://user:pw@localhost/db --klock ./gate.klock
$ prorm db serve ./.klock --port 7070                 # a passwordless gate on 127.0.0.1:7070 (alias db gate)
The database password is not in the klock. When the URL carries a
password, db init writes the URL without it, puts the password in the
.env in the klock's directory, and records where it is as
urlSecretRef: "env:<NAME>". The name is PRORM_KLOCK_DB_PASSWORD for
.klock, or PRORM_KLOCK_DB_PASSWORD_<NAME> for another file name
(gate.klock → PRORM_KLOCK_DB_PASSWORD_GATE). A new .env is created
with mode 0600; an existing one keeps its mode and gets the line added or
replaced. db init warns, <file> may not be git-ignored — add it to .gitignore so credentials aren't committed., when no .gitignore nearby
seems to cover it.
Readers put it back. db serve, readLocalKlock() and LocalKlock*
read $<NAME> from the environment first, then from that .env. A
keychain:<service>/<account> reference is read from the OS keychain. When
neither has it, the error says which variable to set. A version 1 local
klock written before this change may still carry the password in its url:
it still works, with a warning to re-run db init.
The compose file that db init --docker writes still names the
password in plain text (unlike db up's, which reads .env).
The 0600 rule applies. A klock that group or others can read is
refused, except on Windows.
The gate that db serve runs admits a single-use request token
(Authorization: Klock kl2.…, which LocalKlock.token() makes), or the
rotating session id once per 30-second window. It remembers what it has
admitted until the window passes, so a replayed token gets 401 (the token has already been used). A gate klock may also carry an optional scope
({"ops": ["read"], "tables": ["orders"]}) that the gate checks each
statement against, answering 403 outside it. db init does not write a
scope; add it by hand. The full rules are in
klock.
A client in the same project mints a fresh token for every request:
import { LocalKlock } from 'ts-prorm-orm';

const klock = LocalKlock.fromFile('./.klock');

const res = await fetch('http://127.0.0.1:7070/query', {
  method: 'POST',
  headers: {
    authorization: `Klock ${klock.token()}`,
    'content-type': 'application/json',
  },
  body: JSON.stringify({ sql: 'SELECT count(*) AS n FROM orders' }),
});
const { rows } = (await res.json()) as { rows: Array<{ n: number }> };
GET /healthz is open, for a container health check.

Related reading#

Extensions — extensions list/import/export, and new Prorm({ extensions })
CLI — every command
klock — the local klock db init writes, and the db serve gate
docker-compose.test.yml — the integration-test stack these images follow
Modified at 2026-10-04 00:40:16
Previous
klock — the `.klock` file
Next
Structured log search — Splunk, Graylog, Papertrail, Sumo Logic
Built with