opsdocsConsole

Ops platform / Resources

Databases

Postgres, MariaDB, libSQL or SQLite, on servers that belong to your project alone.

Updated 13 Oct 2026View as Markdown

A database runs on a server of your project's own, never shared with another project. It is reachable only from the services of its environment, so nothing outside ops connects to it. Ops writes the connection string into a secret group; you never type a password.

Adding one

Open Databases and choose Add database:

fieldwhat it does
Enginepostgres (default), mariadb, libsql or sqlite
Write the connection string intothe secret group that receives it; not for SQLite
Variablethe variable name, default DATABASE_URL
ExtensionsPostgres extensions to install
Keep a template for previewsPostgres and MariaDB: an idle copy previews are cloned from
Adopt an existing databasePostgres: name a database that already exists; ops watches it and never creates or drops it

Ops writes the connection string into the group. Render the group, see Secrets, and the services that mount it start with it.

What each engine hands your service

For the variable DATABASE_URL:

enginevariables in the group
postgresDATABASE_URL=postgres://<login>:<password>@<server>:5432/<database>
mariadbDATABASE_URL=mysql://<login>:<password>@<server>:3306/<database>
libsqlDATABASE_URL=libsql://<server>:8080?tls=0&authToken=<token>, plus DATABASE_URL_HTTP_URL and DATABASE_URL_AUTH_TOKEN for clients that want them apart
sqlitenone; the service that mounts it gets DATABASE_URL=sqlite://<path>/<name>.sqlite

Every value written to the group is a secret: ops env pull names them and never shows them. For local work, point your own DATABASE_URL at a local database.

SQLite

SQLite has no server. It is a file on a volume mounted into one service:

ts
services: {
  notes: { dockerfile: 'Dockerfile', port: 3000, volumes: [{ database: 'notes', path: '/data' }] },
},

Create the sqlite database called notes in each environment first. The service then runs one replica, stopped before the new one starts, on the machine that holds the file, because SQLite has one writer. A service mounts at most four databases, and each database is mounted by one service. A preview gets its own empty one.

Templates and previews

A Postgres or MariaDB database that keeps a template has an idle copy beside it, which you fill with seed data. Each preview gets a fresh copy of the template, owned by a login of its own. Never put production data in a template: a preview runs code nobody has reviewed yet.

Rotating and restoring

The database's page lists its connection with the credential masked. Rotate sets a new password (a new token for libSQL), writes it into the group and restarts the services that use it. Restore to a point in time shows how a restore runs for its engine.

Per customer databases

A product that keeps one database per customer asks ops for them through the API, as databases of kind tenant. They get no connection string in a group, and the Databases page shows them as a count with a search box rather than a list.