Skip to main content
The database service runs managed clusters for you. You pick an engine, a size and a subnet; the platform provisions the members, publishes a stable hostname for connections, takes scheduled backups, and moves the endpoint when the primary changes. The service is regionaldatabase.sa-saopaulo-1.basaltic.sh. A cluster lives in one region, on one of your subnets.

Create a cluster

Engine, flavor, storage and placement — and what a create refuses before it provisions anything.

Connect

The endpoint you are given, where its password lives, and why you connect by name.

High availability

Converting a single node in place, adding replicas, and what a failover actually does.

Backups and restore

The schedule, the retention window, and restoring in place versus into a new cluster.

Parameter groups

What you may tune, and when a change actually reaches the engine.

Access control

Actions, CRNs, and the tag condition that will not do what you expect.

Engines and versions

GET /v1/engines is the catalogue. You choose a major version only — minor and patch releases inside that major are applied for you. Omit engine_version on create and you get the engine’s default_version from that catalogue. Read it from the API rather than assuming — the default moves as majors are added.
The engine and its major version are fixed for the life of the cluster. There is no in-place major upgrade. Moving from 17 to 18 means creating a new cluster — see restoring into a new cluster for the path that carries your data across.

What each engine supports

The two engines are not the same product with a different port. Most of the day-2 surface is postgres-only, because it is what the topology underneath can actually do.
A valkey cluster is either a single node or a three-member quorum. Two members cannot hold a stable quorum, so instance_count: 2 is refused rather than provisioned into something that cannot fail over.

Creating a cluster

name, engine_type, flavor_id, storage_gb and networks are required. The response is 202 with the cluster in pending; poll GET /v1/clusters/{cluster_id} until status is active.
1

Pick a database-family flavor

Cluster members book flavors reserved for this product:
A general or loadbalancer flavor is refused with a 400 naming the family it found. The check runs before anything is reserved, so a wrong flavor costs you a round trip and nothing else.
2

Size the storage

storage_gb is the disk given to each member, so a three-node cluster of storage_gb: 100 consumes three 100 GB volumes.
Storage, flavor, engine and node count are immutable through PATCH /v1/clusters/{cluster_id} — that path edits description, tags and the parameter-group binding only. Size for where you are going.
3

Place it on a subnet

networks takes at least one interface, each naming a subnet_id and optionally security_group_ids. Every member lands on the same subnets, and a replica added later inherits that placement. See Networking for subnets and security groups.
4

Choose the exposure

assign_public_ip defaults to true: each endpoint gets a floating IP and is reachable from the internet. false allocates no floating IP — endpoints resolve to member addresses inside the VPC, which is how you place a cluster on a private subnet.
A public cluster requires its subnet to carry a default route (0.0.0.0/0) to an internet gateway. Without one the create is refused up front — attach a gateway and add the route, or set assign_public_ip: false.
The setting governs IPv4 only. IPv6 reachability follows the subnet’s ::/0 route either way, exactly as it does for any other resource.
5

Wait for active

Members report their own readiness once the engine is up, and the cluster flips building to active when the whole set has reported. First boot installs and initialises the engine, so allow minutes, not seconds.

Naming, quota, and where the members show up

unique per account
Matches ^[a-zA-Z0-9](?:[a-zA-Z0-9_.\-]{0,126}[a-zA-Z0-9])?$. It becomes the first label of the cluster’s endpoint hostname, so a DNS-friendly name gives you a DNS-friendly endpoint.
database / instances, per region
Quota counts members, not clusters — a three-node cluster consumes three. Adding a replica reserves one more before the VM is created.
visible in compute, read-only
Each member is an instance in your account and appears in GET /v1/instances with managed_by: "database". It is there to be seen, not driven: start, stop, reboot and delete against a managed instance all 404. Manage the cluster through this API.
key_names is refused on a database cluster. The members run the platform’s own software and there is no SSH path to them — the endpoint is the whole tenant-facing surface.

Connecting

A cluster publishes its connection points in endpoints: Each entry carries dns_name, port, and ip_address — the address the name currently answers with.
Connect by dns_name, never by ip_address. The address is resolved per request and is not stable: on a VPC-only cluster the writer’s address changes the moment the primary changes. Use ip_address for the things a name cannot express, such as a security-group rule.
The name itself is deterministic — the cluster’s name and your account handle inside a per-region database zone, with the reader carrying a -ro suffix on the cluster label:
The zone suffix belongs to the region, so take the finished value from endpoints[].dns_name rather than assembling it yourself.

Credentials

The cluster is created with a bootstrap admin role named by admin_user (default admin) and, on postgres, a database named by default_database (default default). Its password is not returned by the API. It is stored in the secrets service, and the cluster reports the id: read admin_secret_id from the cluster, then fetch that secret. The same holds for every database user you create — password_secret_id points at the secret that holds it.
A database user’s plaintext password is returned once, on the create and rotate-password responses only. GET and LIST never include it. Capture it, or fetch it from the secret it points at.
Password authentication is SCRAM-SHA-256 for postgres.
A cluster created with assign_public_ip: true answers on the public internet. Nothing else fences it — put a security group on the members’ interfaces that admits only the sources you intend, and confirm the rule is attached to the interface rather than merely defined. See Networking.

High availability

A cluster is HA when it runs more than one member. Postgres starts HA at two members and grows to ten; valkey is HA only at three. You do not have to decide on day one. A single postgres node is the cheap first choice, and there is a path from it to HA that keeps the endpoint and the data.

Converting a single node

This adopts a running single-node postgres cluster into an HA-capable one in place. It keeps the cluster’s endpoint, its data and everything attached to it — the alternative was dumping into a second cluster and re-pointing every connection string by hand.
This restarts the engine, on your only copy of the data. A single-node cluster has no replica to serve while its node comes back up under the new supervisor. Convert during a window where a brief outage is acceptable.
The call answers 202 and the cluster reports converting. It returns to active when the member reports the outcome, and is marked HA-capable (patroni_managed: true) only on success — a conversion that fails leaves the node serving exactly as it was. A conversion that is never reported is abandoned after 30 minutes rather than holding the cluster in converting forever. It is refused with 409 if the cluster is already HA-capable, a conversion is already in flight, the cluster is not active, or it does not have exactly one member.

Replicas

A replica is a streaming read replica: another member, on the same flavor, storage size and subnets as the primary, that seeds itself from the leader and then follows it. The request body is empty today.
Adding a replica requires the cluster to be HA-capable, which is not the same question as its node count. A cluster created single-node has nothing for a replica to join and is refused with a pointer to convert-to-ha; a converted cluster has one member and accepts replicas immediately. The flag to read is patroni_managed, not instance_count.
The first replica also brings up the reader endpoint, so a cluster that grew into HA ends up with the same -ro hostname as one that was born HA. Removing a replica requires the cluster to be active, and the primary can never be removed — fail over first, which demotes it, then remove it. Valkey refuses removals that would leave it at two members.

Failover

This is a planned switchover, not a forced promotion, and it is safe to trigger deliberately — that is what it is for. Use it to drain a member before maintenance, or to prove your failover story before you need it. Leave target_member out and the engine picks a candidate. Supply one — the member_name of any member from the cluster’s instances array — and that member is promoted. Naming a member that is not in the cluster, or the current primary, is a 400. What moves:
  • The writer endpoint follows the new primary. On a public cluster the floating IP is rebound, so the address does not even change. On a VPC-only cluster the record is republished at the new primary’s address — which is why you connect by name.
  • The reader endpoint is re-pinned off the newly promoted member onto another live replica.
The cluster reports failing-over while the switchover is pending, and returns to active when the transition is reported. A pending switchover that is not executed within 10 minutes is cleared and the cluster rolls back to active — retry it. The cluster must be active and HA. A single-node cluster has nothing to switch to and is refused.

Users and databases

This whole section is postgres-only. Valkey has no logical databases and no per-user management — engine "valkey" has no logical databases or db users is the refusal. Connect to a valkey cluster with its admin_user and the admin secret.
Both answer 202. The API records what you asked for and the cluster converges on it moments later, so a role or database is not necessarily usable the instant the call returns — check for it on the cluster before pointing an application at it. permissions is an engine-specific grant map carried through to the cluster.

Reserved names

Names must be postgres-safe identifiers — a letter or underscore, then letters, digits and underscores, up to 64 characters. On top of that: The platform-managed principals are excluded because the cluster’s own replication, bootstrap and coordination depend on them; renaming or dropping one from underneath would break the cluster rather than the application.

Rotating a password

Issues a fresh password, writes it to a new secret, and returns it once. The cluster’s own admin_user cannot be rotated through this path — its secret belongs to the bootstrap path.

Backups and restore

Every cluster gets a backup repository at create. Postgres additionally archives its write-ahead log continuously, which is what makes point-in-time recovery possible between full backups.

The schedule

With a weekly full and two kept, the recoverable window reaches back roughly two weekly cycles.
Retention is enforced in the backup repository, not in the catalogue. A row in GET /v1/clusters/{cluster_id}/backups is a record of a backup that ran — it is not by itself proof that the data is still inside the retention window.

Taking one yourself

kind is base (the default — a full copy, needing no earlier backup to build on) or incremental (only what changed since the last one). full and incr are accepted as aliases on both the request and the list filter, but the backup is stored and returned as base or incremental. The cluster must be active or building. A backup that never reports within six hours is marked failed.
Valkey takes full backups only — its backup path is a complete dump every time, so incremental is refused for that engine.

Restoring in place

This overwrites this cluster’s data and keeps everything else: the same endpoint, security groups, IAM role and parameter group. It is the “undo a bad migration” path — the restore was never the hard part, the cutover was.
Destructive and irreversible. confirm must equal the cluster’s name, so consent cannot arrive by accident from a retried request, a stale tab, or a script looping over ids.
Before anything is overwritten, a pre-restore backup is requested automatically — if that backup cannot be started, the restore does not happen. That is the way back from a mistaken restore. The call answers 202 and the cluster reports restoring, which is neither active nor building: while it holds, the cluster must not be read as serving current data. An HA cluster is restored as a whole — the leader is restored and the replicas are rebuilt from it. A restore that fails leaves the cluster serving, with a fault recorded and the pre-restore backup still available. recovery_target_time (RFC 3339) replays the log forward from the backup to that instant and stops there. Omit it to recover to the latest archived point. A target earlier than the backup’s earliest_restore_at is refused. The source backup may belong to this cluster or to another cluster in the same account; a cross-cluster restore grants this cluster read on that repository for the duration and revokes it afterwards. Postgres only. The cluster must be active, and a second restore while one is in flight is a 409.

Restoring into a new cluster

Pass restore_from on create to bootstrap a new cluster from a backup instead of an empty database:
This is the route around every immutable field: a bigger disk, a different flavor, a newer major version. Create the new cluster from a backup, verify it, then move your connection strings. HA works here too — only the bootstrap primary is seeded from the backup, and the replicas rebuild from it. The source backup must belong to the same account and be succeeded. Valkey does not support it.

Choosing a backup

GET /v1/clusters/{cluster_id}/backups lists what ran. Two fields decide whether a given row can be restored:
must be succeeded
running and failed rows are not restore targets.
boolean
true means the backup can be selected by name. false means it predates the platform recording backup labels, so it can only be restored while it is the source cluster’s most recent succeeded backup — honouring it otherwise would silently restore a different backup than the one you picked.
Deleting a cluster deletes its backup catalogue. The stored data is held for a recovery window before it is purged, but the rows the API restores from are gone with the cluster, so there is no self-service path back. Take a backup and restore it into a second cluster before you delete the first.

Parameter groups

A parameter group is a named set of engine settings that clusters bind to. Bound clusters converge on the group’s settings, so editing the group reaches every cluster using it.
Bind it at create with parameter_group_id, or later with PATCH /v1/clusters/{cluster_id}. An empty string on the patch clears the binding and returns the cluster to engine defaults.
A group must match the cluster’s engine type and major version exactly. A postgres 17 group on a postgres 18 cluster is not a near miss — settings are version-specific, and binding across versions would apply parameters the engine may not have.

What you can tune

That endpoint is the allowlist: every tunable setting, its value grammar, its bounds, and whether applying it needs a restart. Read it rather than discovering the list by collecting 400s — a group naming an unlisted setting is rejected outright, not quietly dropped. Settings the platform owns are absent and refused, and the reasons are worth knowing:
  • wal_level, archive_mode, archive_command, max_wal_senders — backups and replication are built on these.
  • listen_addresses, port, data_directory, unix_socket_directories — provisioning assumes them.
  • hot_standby, primary_conninfo, synchronous_standby_names, recovery_target* — owned by the HA supervisor.
  • shared_preload_libraries — a bad value stops the server booting at all.
On valkey the same logic excludes bind, port and tls-* (the listener), requirepass, aclfile and user (how the managed user model authenticates), and dir, dbfilename and appendfilename (where the backup path looks). Values are validated against the grammar and bounds: 256MB and 30s are accepted with units, and bounds are compared in the base unit, so 1GB and 1024MB are the same value. A rejection names the offending key.

When a change takes effect

Each parameter reports an apply value:
The platform will not bounce a cluster to apply a config edit. Set a restart parameter — max_connections and shared_buffers are the common ones — and the cluster keeps running on the old value until it restarts for some other reason. Check apply before you plan a change around it.

Editing and deleting

PATCH /v1/parameter-groups/{parameter_group_id} with params replaces the whole set, and republishes it to every bound cluster.
A setting you drop from the group is not left behind on the clusters — they converge on the new set, so it reverts to the engine default. Send the complete map you want, not just the keys you are changing.
A group that is still bound to a cluster cannot be deleted; the 409 names the clusters holding it. Move them off first.

Access control

Every operation checks an IAM policy. Cluster and backup actions authorize against the cluster’s CRN, parameter-group actions against the group’s:
These CRNs are keyed by id, not by name, so you cannot fence a naming convention the way you can for a name-based resource. Fence with tags instead — see below and Policies.
Requesting a backup needs database:CreateBackup, even though the operation is requestBackup and the audit trail records it as database:RequestBackup. Grant the action, not the verb in the path.
Cluster and parameter-group actions carry tag context, so basalt:ResourceTag/<key> conditions work on them, and basalt:RequestTag/<key> fences what a create or update may label a resource as.
The user and logical-database actions carry no tag context. An allow conditioned on basalt:ResourceTag/... fails closed on database:CreateDBUser, database:CreateDatabase and their siblings, because the key is absent from the request — so a tag-scoped grant that works for the cluster will deny managing users inside it. Grant those actions without a tag condition, or scope them by CRN.
Reads of the whole collection — ListClusters, ListParameterGroups — are authorized against the account-wide CRN (cluster/*), so a policy can grant listing in one account or region and not another.

Cluster statuses

Troubleshooting

A cluster reaches active only when every member reports readiness, and first boot installs and initialises the engine from scratch. Minutes are normal. If it has not moved much longer than that, read fault on the cluster and check instances[].status — a member that never reached running is a provisioning problem, not a database one.
Read patroni_managed, not instance_count. A cluster created single-node has no HA machinery for a replica to join and is refused until you run POST /v1/clusters/{cluster_id}/convert-to-ha. After a successful conversion the same request works unchanged — even though the cluster still has exactly one member.
A pending switchover has to be picked up and executed by the current leader. One that is not executed within 10 minutes is cleared and the cluster is rolled back to active so it is not stuck in failing-over. Retry it. If it keeps expiring, check that the current primary is healthy in instances.
Check the parameter’s apply in GET /v1/engines/{engine_type}/parameters. A restart parameter is stored immediately and takes effect only at the engine’s next restart — the platform does not restart a cluster to apply a config edit.Also confirm the cluster is actually bound: parameter_group_id on the cluster has to name the group you edited.
Three checks, in order. confirm must equal the cluster’s name exactly. The cluster must be active — a cluster in restoring, converting or error will not accept one. And the source backup must be succeeded with restorable: true, or else be the source cluster’s most recent succeeded backup.
Work outward. Confirm you are connecting to endpoints[].dns_name and the endpoint’s port — 5432 for postgres, 6379 for valkey. Confirm the exposure matches where you are calling from: assign_public_ip: false means the endpoint only answers inside the VPC. Then check that a security group admitting your source is attached to the members’ interfaces — rules that exist but are not on the interface do nothing. See Networking.
Both calls answer 202. They record intent, and the cluster converges on it a moment later. Poll the cluster’s users or databases before pointing an application at one.

Next

Networking

Subnets, security groups and the internet gateway a public cluster needs.

Policies

Writing the conditions that fence who may touch which cluster.

API reference

Every cluster, user, backup and parameter-group operation, with schemas.

Support

When a cluster is in error and fault does not explain it.