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
| Engine | Value |
|---|---|
| PostgreSQL | postgres |
| Redis | redis |
| MariaDB | mariadb |
| MongoDB | mongodb |
| ClickHouse | clickhouse |
otterdeploy add database postgres --engine postgresOther 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.
| Engine | What you get |
|---|---|
| PostgreSQL | Table list with row estimates, a SQL console, and an editable row grid |
| MariaDB | Tables and rows |
| MongoDB | Collections and documents |
| Redis | Keyspace, keys and values |
| ClickHouse | Not 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: write—executefor arbitrary statements, andmutateRowfor 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.