Create and connect a database
Create a PostgreSQL, Valkey, MySQL or MongoDB database service, connect services to it, and run queries, readers, point-in-time recovery, dump backups and restores.
A database service runs inside your cluster like any other service and is reachable only from inside it. This guide creates one, connects a service, and covers queries, readers, recovery and backups.
Before you begin
- You have the deployer role to create, connect and query a database. Admin is needed to restore, to query or configure recovery in a production environment, and to take, schedule or download a backup in one.
- The environment is bound to a cluster. A PostgreSQL database also needs CloudNativePG on that cluster.
- Backups need an organization object store.
Create a database
Right-click the project canvas and select Add database…. The Add a database dialog opens for the current environment.
Under Database engine, pick a card, enter a Name and select Create database. A name gives a slug of at most 40 characters, which becomes the host svc-SLUG.
The new database appears on the canvas as a staged tile, and nothing deploys yet. Review the change set in the bar, then select the apply button: Deploy N changes, or Request approval in a production environment.
To create the database through the API, name the environment to stage its first deploy in:
curl -X POST "BASE_URL/api/v1/projects/PROJECT_ID/databases" \
-H "Authorization: Bearer NEBULA_TOKEN" \
-H "Content-Type: application/json" \
-d '{"template": "postgres", "name": "main", "stageIn": {"environmentId": "ENVIRONMENT_ID"}}'template is postgres, valkey, mysql or mongodb (listDatabaseTemplates). Without stageIn, nothing is staged.
Every database gets the newest major version, a 10 GiB volume, and one process with 250m–1 CPU and 256 MiB–1 GiB of memory.
| Engine | Newest | Also runs | Port | Variables it defines |
|---|---|---|---|---|
| PostgreSQL | 18 | 16 | 5432 | DATABASE_URL, DATABASE_READ_URL, POSTGRES_PASSWORD, POSTGRES_USER, POSTGRES_DB |
| Valkey | 9 | 7 (Redis image) | 6379 | REDIS_URL, REDIS_PASSWORD |
| MySQL | 8 | 3306 | MYSQL_URL, MYSQL_ROOT_PASSWORD, MYSQL_DATABASE | |
| MongoDB | 7 | 27017 | MONGODB_URL, MONGO_INITDB_ROOT_PASSWORD, MONGO_INITDB_ROOT_USERNAME |
To pick an older major or a size other than 10 GiB at creation, declare the database with version and size in the project file.
Connect a service
Select the database, open its Data tab and find the Connected services card.
Open the Choose a service list, pick a service of the project and select Connect. The change is staged: the service gets the variable DATABASE_URL_PG_SLUG, whose value is a reference to the database's URL. The other names are REDIS_URL_SLUG, DATABASE_URL_MYSQL_SLUG and DATABASE_URL_MONGO_SLUG; SLUG is upper case with - as _.
If the service already has an empty DATABASE_URL, a Fill DATABASE_URL button appears next to Connect. It sets that variable to ${{ SLUG.DATABASE_URL }} instead of adding a second one. The fillable names are DATABASE_URL, POSTGRES_URL, PG_URL (PostgreSQL), REDIS_URL, VALKEY_URL, CACHE_URL (Valkey), MYSQL_URL and MONGODB_URL.
Deploy the change set. The service restarts with the URL resolved.
To read the URL yourself, use the Connection row on the Data tab: Reveal shows it for 30 seconds, Hide hides it, Copy copies it. PostgreSQL also shows Read connection. Reveal needs the deployer role; without it, Copy gives the masked URL.
Connect writes only the main variable. To send reads to the reader endpoint svc-SLUG-read, add a variable on the service with the value ${{ SLUG.DATABASE_READ_URL }}. It points at the writer until the database has readers.
Run a query
On the Data tab, pick a table, collection or keyspace to preview five rows, or type in the Query box and select Run (Ctrl or ⌘ + Enter). Every query runs on the writer, stops after 5 seconds, returns at most 500 rows and is written to the activity log, so never paste a secret into one. The database must be deployed.
- PostgreSQL and MySQL: one statement starting with
SELECT,WITH,EXPLAIN,SHOW,TABLEorVALUES(MySQL alsoDESCRIBE,DESC), in a read-only transaction. A semicolon may only end it. - Valkey: one read command, such as
GET key,HGETALL keyorSCAN 0 COUNT 50. - MongoDB: a JSON document,
{"find": "COLLECTION", "limit": 50}or{"countDocuments": "COLLECTION"}.
Add readers and fail over
PostgreSQL databases have a Replicas tab.
- Set Read replicas to 0–5. The change is staged and applied without downtime.
- Read each reader's Streaming or Syncing state and lag. To make a reader the writer, select Promote, then Promote to writer (
promoteDatabaseReader). It takes effect at once. The old writer rejoins as a reader, and connections to the writer drop for a few seconds. - The Automatic failover switch needs at least one reader. When it is on, a writer unreachable for 30 seconds is replaced by the most caught-up reader. It is on by default once a reader exists.
Turn on point-in-time recovery
On a PostgreSQL database's Backups tab, turn on Point-in-time recovery. In Turn on point-in-time recovery, choose the Object store, enter an Access key ID and Secret access key, optionally a Prefix (default PROJECT/SLUG/ENVIRONMENT), then select Turn on. Use a key scoped to that prefix only, never the organization's main key. The cluster needs the Barman Cloud Plugin, which the CloudNativePG install adds. In production this step needs admin.
To restore, enter a moment in the UTC field and select Restore to this moment…, then Restore (admin). The console refuses a future moment and, once the archive reports its range, an earlier one than it holds. The restore creates NAME-restored-YYYYMMDD-HHMM with the same version, size, CPU and memory, and deploys it at once; the original is not touched. In this environment the new database keeps the original's password; its other environments start empty. In production the deploy waits for approval. Point services at the new database once you have checked it. The API operations are putDatabasePitr and restoreDatabasePitr.
Back up and restore from a dump
When the organization has an object store at creation, each environment gets a daily dump, taken between 02:00 and 04:59 UTC, keeping the last 7. Without one, the console warns "has no scheduled backups". Add a store later, then on the Backups tab select Schedule…. Dumps run while the database keeps serving.
To restore PostgreSQL, Valkey, MySQL or MongoDB from a dump:
On the Backups tab, select Restore on a backup, or Restore… and under Restore from choose From an object key.
Under Restore into, keep Into a new service. It creates NAME-restore next to the original with the same version and size and new credentials, deploys it, and loads the dump once it is healthy (the restore fails after 2 hours). The original keeps serving. Select Restore to new service.
Choose In place only to overwrite the database. The console warns that the service stops, its data is wiped, and it stays stopped if the restore fails. Type the service name, then select Restore and stop NAME.
Restoring needs admin. Progress shows under Restore history. After a restore into a new service, connect your services to it and delete the old one when you no longer need it.
Move to a newer major version
A database keeps the major version its data was created under. To move to the newest:
- On the Backups tab, select Back up now on the old database. Read the
objectKeyof the succeeded backup withGET BASE_URL/api/v1/volumes/VOLUME_ID/backups?environmentId=ENVIRONMENT_ID(listVolumeBackups). - Add a new database as above and deploy it.
- On the new database's Backups tab, select Restore…, From an object key, the same object store and the key. Under Restore into, choose In place, type the new database's name to confirm, then restore. The new database is empty, so nothing is lost.
- Check the data on the Data tab, connect your services to the new database, then delete the old one.
Install CloudNativePG on a cluster
A newly installed cluster already has it. Without it, deploying a PostgreSQL database is refused with "this cluster doesn't run CloudNativePG yet: install it from the cluster's settings (Install CloudNativePG), then deploy again", and the database's Settings tab shows an Install CloudNativePG button that opens the cluster's settings. An admin installs it from Clusters: open the cluster, then the Settings tab. Under CloudNativePG, select Install CloudNativePG. The console shows an Install command and a One-time token. Run the command on the cluster's first server and paste the token at the NebulaCtrl token: prompt. It installs cert-manager, the Barman Cloud Plugin and the operator, applies this release's custom resource definitions, and is safe to run again with a new token (installClusterCnpg).
Stop or delete a database
On the database's Settings tab, Stop in ENVIRONMENT scales every process to zero in that environment and withdraws its domains. The release, variables and volumes stay, and Start in ENVIRONMENT brings back what ran. A PostgreSQL database hibernates its CloudNativePG cluster instead, which keeps its volumes.
Delete service removes the database and its volumes in every environment. Back it up first.
Hold Delete service to confirm. Service variables whose whole value is a reference to the database's connection URL (DATABASE_URL, or DATABASE_READ_URL on PostgreSQL), and that no nebula.toml sets, are removed with it. Any other referring variable blocks the delete with a message such as "SLUG is still referenced by KEY on SERVICE in ENVIRONMENT: remove or change those variables first, then delete it". It names up to five referrers, then "and N more".
Verify
The database shows healthy on the canvas, its Data tab lists the service under Connected services, and SELECT 1 in the Query box returns a row.
Next steps
Expose a service only through Tailscale
Connect a cluster to your tailnet, then give a service a tailnet-only domain under your ts.net name so only devices signed into your tailnet can reach it.
Roll back or promote a release
Roll a service back to a release that ran before, redeploy its active release, or promote a release's image to another environment, from the console or the API.