---
title: Databases
description: Postgres, MariaDB, libSQL or SQLite, on servers that belong to your project alone.
order: 2
---

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**:

| field | what it does |
| --- | --- |
| Engine | `postgres` (default), `mariadb`, `libsql` or `sqlite` |
| Write the connection string into | the secret group that receives it; not for SQLite |
| Variable | the variable name, default `DATABASE_URL` |
| Extensions | Postgres extensions to install |
| Keep a template for previews | Postgres and MariaDB: an idle copy previews are cloned from |
| Adopt an existing database | Postgres: 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](secrets.md), and the services that mount it start with it.

## What each engine hands your service

For the variable `DATABASE_URL`:

| engine | variables in the group |
| --- | --- |
| `postgres` | `DATABASE_URL=postgres://<login>:<password>@<server>:5432/<database>` |
| `mariadb` | `DATABASE_URL=mysql://<login>:<password>@<server>:3306/<database>` |
| `libsql` | `DATABASE_URL=libsql://<server>:8080?tls=0&authToken=<token>`, plus `DATABASE_URL_HTTP_URL` and `DATABASE_URL_AUTH_TOKEN` for clients that want them apart |
| `sqlite` | none; 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](../concepts/deployments-and-previews.md#previews) 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.
