Guides

Databases

Provision Postgres, Redis, MariaDB, MongoDB or ClickHouse alongside your services.

A database is a first-class resource, not a container you wire up yourself. It gets provisioned next to the services that use it, and those services reference it by name.

Engines

EngineValue
PostgreSQLpostgres
Redisredis
MariaDBmariadb
MongoDBmongodb
ClickHouseclickhouse
otterdeploy add database postgres --engine postgres

Other data services are templates

RabbitMQ, MinIO, Meilisearch and similar are available as compose stack templates, not as managed database engines. The five above are the whole database_engine enum.

Provisioning reports live progress — image pull, then provision — rather than a spinner that resolves into either "ready" or nothing.

Connecting a service

Don't paste a connection string. Reference the database and let the resolver fill it in at deploy time:

{
  "env": {
    "DATABASE_URL": "${database:postgres.DATABASE_URL}"
  }
}

Individual fields are addressable too — url, host, port, username, password, database — so you can compose a string yourself when a library insists:

{
  "DSN": "postgres://${database:primary.username}:${database:primary.password}@${database:primary.host}:${database:primary.port}/app"
}

Because the lookup is by name at deploy time, the same manifest resolves to a preview's own database copy when it runs in a preview. See environment variables and previews.

Reaching it from outside

By default a database is only reachable inside the cluster. To connect from your laptop or an external tool, expose it at layer 4 — the same Caddy edge routes raw TCP to it.

Use this deliberately. An exposed database is on the public internet, protected by its credentials and whatever the firewall blocks.

Ephemeral credentials

Short-lived roles can be issued for a database instead of handing out the main credentials. They expire on their own — Postgres's VALID UNTIL stops new logins at expiry — and a sweeper runs every minute to terminate live sessions and drop the role behind them.

Backups

Databases are backed up by logical dump, encrypted, on a schedule you set. See backups.

Browsing the data

Each database resource has a Data tab — you don't need to expose it or reach for a separate client to look inside.

EngineWhat you get
PostgreSQLTable list with row estimates, a SQL console, and an editable row grid
MariaDBTables and rows
MongoDBCollections and documents
RedisKeyspace, keys and values
ClickHouseNot covered by the browser

Queries run inside the database's own container, so nothing is exposed to the network to make this work.

Reads and writes are separate permissions

  • database: read — list tables, collections, keys.
  • database: query — run a SQL query. Postgres runs it in a read-only session (default_transaction_read_only=on), so a write in the console errors at the server rather than relying on the UI to stop you.
  • database: writeexecute for arbitrary statements, and mutateRow for grid edits, which builds its statement from structured input rather than passing your text through.

Every write records who ran what: execute puts the statement on the request's wide event, so the audit log captures the actual SQL.

Templates

Eighteen curated compose stacks ship in the dashboard's template gallery — Gitea, n8n, Metabase, Authentik, Plausible, Umami, Vaultwarden, MinIO, RabbitMQ, Meilisearch, NocoDB, Directus, Ghost, Twenty, Excalidraw, Uptime Kuma, RustFS and a Grafana + Prometheus pair.

Each template declares the environment it needs — with a hint for generating each secret — before it will deploy, so you don't get a stack that comes up and then fails on a missing key.