Databases

Databases

A managed PostgreSQL database: you choose a version and a plan, SwiftCloud runs the server, keeps its storage, takes backups when you turn them on, and hands you a connection string. You never manage a server or log in to one.

At a glance

  • Engine: PostgreSQL only. Pick a version when you create the database.
  • Two ways in: a public hostname on port 5432 with TLS required, for your laptop, CI or servers elsewhere; and a short private name such as db-orders for apps in the same organisation.
  • Plans fix the CPU, memory and disk. You can move up to a bigger plan, not down.
  • Backups are off until you turn them on. Once on, you get a daily backup plus a continuous record of every change, so you can restore to any moment in the retention window.
  • Restore always creates a new database. The original is never overwritten.
  • Billing: a flat hourly price for the plan, charged from your organisation's wallet. A stopped database costs the same as a running one; only deleting it stops the charge.

Use a managed database when your app needs relational storage and you would rather not run PostgreSQL yourself. If you need an engine other than PostgreSQL, or superuser access, run it on a virtual machine instead.

PostgreSQL

Every database is a single PostgreSQL server in region zm-lsk-1 (Lusaka), with its own disk. The versions on offer come from a catalogue the platform maintains; at the time of writing they are 17 (the default), 16 and 15. The console's version list and cloud db engines always show the current set, with the default marked.

cloud db engines

What you get inside the server:

  • One database named app and one user named app, which owns it. The password is generated for you. Create your own schemas, tables and further roles inside it as you like.
  • The app user is the database owner, not a superuser. Extensions that PostgreSQL marks as trusted (for example pgcrypto, citext, hstore, pg_trgm, uuid-ossp) can be added with CREATE EXTENSION; extensions that need a superuser cannot.
  • Up to 100 connections at once. Use a connection pool in your app rather than opening a connection per request.
  • PostgreSQL's shared buffer cache is set to about a quarter of the plan's memory, and adjusts when you change plan.
  • There is one server per database, with no standby copy. If it restarts, connections drop for the moment it takes to come back; backups are how you protect the data itself.

Create a database

cloud.co.zm/dash/databases/create
The Create Database page: an organisation and region picker, the PostgreSQL engine card with a version list, the instance name and optional description fields, a list of instance sizes with their CPU, memory, disk and price, and a summary card with the estimated monthly cost and a Create Database button.
Create Database: region, version, name and size, with the estimated cost on the right.
  1. Open Databases and click Create Database (/dash/databases/create).
  2. Choose the organisation that will own and pay for it, and the region (zm-lsk-1).
  3. Engine and version. PostgreSQL is selected; pick a version or keep the one marked Recommended.
  4. Instance name. Unique among the organisation's databases. Use lowercase letters, digits and hyphens, starting and ending with a letter or digit, at most 40 characters. The console, the CLI and the API all apply this same rule, so a name like orders or shop-prod works everywhere. Add an optional description.
  5. Instance size. Each size is a plan with fixed vCPU, memory and disk, and an hourly price (see pricing). The summary shows the estimated monthly cost.
  6. Click Create Database. The database appears with the status Provisioning and moves to Ready on its own, usually within a few minutes. The page updates live; you can leave and come back.

The same from the CLI:

cloud db create orders --engine postgresql --wait
cloud db create orders --engine postgresql --version 16 --size db-2 --region zm-lsk-1 --wait

--engine is required. Leave out --version for the default version, --size for the smallest plan, and --region to use the region in your CLI context or CLOUD_REGION. --wait prints each status change until the database is ready (or fails), for up to 10 minutes by default (--timeout).

What can stop a create:

  • Balance. The wallet must cover about a month (730 hours) of the chosen plan — in the console, the CLI and the API alike. If it is short the refusal says by how much, and the console links you to a top-up for the difference. Restoring a backup into a new database needs the same. A suspended organisation cannot create anything until it tops up.
  • Name taken: A database named "orders" already exists in this organisation. A deleted database releases its name straight away.
  • Limit reached: Database limit reached. See limits.
  • Role. Creating a database starts a charge, so it needs the owner or admin role in the organisation — in the console as well as the CLI and API. The create form tells members and viewers so and keeps Create Database disabled.

Database statuses

ReadyStoppedRestartingStartingProvisioningFailedSuspendedhealthycould not be createdrestartstopstartbalance runs out,grace period endstop-upbillednot billedDelete works from any status and ends the charge.A stop, start or restart that fails shows Failed until the next check finds the database healthy again.Degraded (running, but a problem on our side) is billed like Ready.
How a database moves between statuses. Solid boxes are billed; dashed boxes are not. Degraded (not drawn) is billed like Ready.
StatusWhat it meansBilled
ProvisioningBeing created. Credentials appear once it is ready. If nothing has been built after 15 minutes it is marked Failed with Provisioning did not complete. Delete this database and create it again.No
ReadyRunning and reachable on its public hostname and private name.Yes
RestartingYou asked for a restart; it returns to Ready when the server is back.Yes
StoppedThe server is shut down; data, disk and backups are kept. Nothing can connect.Yes
StartingYou started a stopped database; it becomes Ready once healthy.Yes
DegradedThe server is running but has no public hostname yet. This is on the platform's side and the team is notified; you do not need to do anything. It holds the same resources as a Ready database, so it is charged like one.Yes
SuspendedYour organisation ran out of funds and its grace period ended, so the server was shut down. Data is kept. Topping up brings it back to Ready.No
FailedCreation, a restore, a plan change, or a stop, start or restart did not complete. The page shows the reason. If the server is actually healthy, the next check (every two minutes) puts it back to Ready.No

The console updates statuses live. From the CLI, cloud db get orders shows the current one and --wait on create, start, stop, restart and restore follows it.

Connecting

Region zm-lsk-1 (Lusaka)Your appsame organisationPublic endpointpicks the database bythe hostname in TLSordersPostgreSQLuser app · db appLaptop, CI, serveranywhere on the internetdb-orders:5432TLS requiredstill encrypted end to end<public host>:5432 · sslmode=require
Apps in your organisation use the private name; everything else uses the public hostname over TLS.

Where to find the credentials

Open the database and go to the Credentials tab. Once the database is Ready it shows:

  • Host — the public hostname, unique to this database.
  • Port — 5432.
  • Username and Password — app and its generated password.
  • Connection String (public) — a ready-made postgresql://…?sslmode=require URI, masked until you reveal it.
  • Connection string for your apps — the same credentials with the private host, for apps in this organisation.

The Overview tab also has a Quick connect command that prompts for the password rather than showing it. From the CLI:

# shell variables psql reads (PGHOST, PGPORT, PGUSER, PGPASSWORD, PGDATABASE, DATABASE_URL)
eval "$(cloud db credentials orders --format env)" && psql

# just the public URI
cloud db credentials orders --format url
# postgresql://app:PASSWORD@HOST:5432/app?sslmode=require

# every field, including the private connection string for apps
cloud db credentials orders -o json

Reading credentials — in the console, the CLI or the API — needs the member role or above. A viewer can see that a database exists but not how to log in: the Credentials tab and the Quick connect command say the details are not shown to their role. Until the database is Ready, the CLI answers that the credentials are not available yet rather than printing nothing.

From your apps (private)

An app on App Platform in the same organisation reaches the database by a short private name: db- followed by the database's name, for example db-orders, on port 5432. If two of your databases would get the same short name, the later one gets a short suffix — always copy the exact value from the Credentials tab.

postgresql://app:PASSWORD@db-orders:5432/app

Traffic on this path never leaves the platform, so TLS is not required (the server still offers it if your driver asks). Store the string as a secret environment variable on your app, for example DATABASE_URL. The private name does not resolve from anywhere else — not from your laptop, not from a virtual machine, and not from another organisation's apps.

From outside (public, TLS)

Everything else — your laptop, a CI runner, a virtual machine, a server at another provider — uses the public hostname on port 5432. TLS is mandatory on this path: the public endpoint uses the hostname your client sends in the TLS handshake to find your database, so a connection without TLS has no way to reach it. The encrypted connection is passed straight through to your database, which ends the TLS session itself.

psql "postgresql://app:PASSWORD@HOST:5432/app?sslmode=require"
psql -h HOST -p 5432 -U app -d app        # with PGSSLMODE=require in the environment

SSL modes

  • sslmode=require — use this on the public hostname. The connection is encrypted.
  • sslmode=verify-ca / verify-full — not supported. The server's certificate is issued by the database's own private authority and named for its internal address, not the public hostname, so verification fails.
  • sslmode=disable — fails on the public hostname. Fine (but unnecessary to set) on the private name.
  • prefer / allow — work, but say what you mean: use require.

Your client must send the hostname during the TLS handshake. Clients built on libpq 14 or later do this by default (the sslsni setting); older clients and some drivers do not, and their connections are closed before login — see troubleshooting. In drivers that take options instead of a URI, set SSL on without certificate verification (for example ssl: { rejectUnauthorized: false } in node-postgres).

Rotating credentials

There is no rotate-password action today in the console, CLI or API. The password shown on the Credentials tab is the one the platform generated, and the console, the CLI and the SQL editor all read that stored value.

You can create additional roles of your own inside the database (the app user owns it) and give each application its own login. Those you can rotate yourself whenever you like.

The SQL editor

SQL Editor in the console (/dash/sql-editor) runs queries from your browser, with nothing to install.

  1. Keep the SwiftCloud Databases mode, choose your database from the Database list (each entry shows its organisation) and click Connect. The editor runs a test query and shows Connected. From a database's Overview tab, Or run queries in the browser with the SQL editor opens the editor with that database already chosen.
  2. Type SQL and click Execute Query (or press Ctrl+Enter). Rows appear in a table below.
  • The editor connects as the app user to the app database, over the same public TLS path you would use. The database must be Ready.
  • Running SQL needs the member role or above, the same as reading the credentials: the editor reads and writes every row. The list shows only PostgreSQL databases in organisations where you have that role; databases where you are a viewer are left out.
  • Each run is its own session: a transaction opened in one run is gone by the next. Put BEGIN … COMMIT in a single run.
  • The query runs exactly as written. The Limit field (100 by default, up to 10,000) caps how many rows are sent back to your browser; when a result had more, the editor says Showing the first … of … rows. The limit does not change your query, so the server still does the full work — add a LIMIT to large SELECTs yourself.

External Databases mode connects to a PostgreSQL or MySQL server elsewhere from a host, port, database name, username and password. The connection uses TLS whenever the server offers it (the certificate is not verified) and falls back to plain text only when the server offers no TLS at all. Only hosts on the public internet can be reached: a name that resolves to a private or internal address is refused with Only databases on the public internet can be reached from the SQL editor. For your own SwiftCloud databases, use SwiftCloud mode.

Start, stop, restart

In the console, the buttons are at the top of the database's page: Stop and Restart while it is running, Start once it is stopped.

  • Restart drops every connection and brings the server back with the same data. Use it when the database is stuck or after heavy changes. It works only while the database is running (Ready or Degraded): a stopped database is refused with This database is stopped. Start it instead of restarting it.
  • Stop shuts the server down. Data, disk, backups and credentials are kept; nothing can connect until you start it again. The console asks you to confirm, and reminds you that a stopped database keeps its storage and plan and is charged as before.
  • Start brings a stopped database back through Starting to Ready, with its data exactly as it was.
cloud db stop orders --wait       # Stopped
cloud db start orders --wait      # Starting → Ready
cloud db restart orders --wait    # Restarting → Ready

These need the member role or above; viewers do not see the buttons. A suspended organisation cannot start or restart databases until it tops up.

Changing the plan

A plan sets the database's vCPU, memory and disk. To move to a bigger one: open the database, and on the Overview tab, in Details, click the plan name (… · change), choose a plan and click Save plan.

  • Owner or admin only. A bigger plan changes what the organisation pays, so members and viewers see the plan but cannot change it.
  • Upgrades only. Smaller or equal plans are marked (current or lower) and refused with You can only upgrade your plan, not downgrade. Disks only grow.
  • The new CPU and memory are applied straight away with a brief restart, so open connections drop for a few seconds. The disk grows while the database keeps running.
  • The new price applies from the next billing hour.
  • If the change cannot be applied, the database shows Failed with The plan could not be applied; contact support.

Changing plan is a console action; the CLI and API do not offer it yet. If you need a smaller plan, create a new database on it and copy the data across (for example with pg_dump and pg_restore).

Backups

Backups are off when a database is created. Turning them on starts two things:

  • A daily base backup — a full copy of the database, taken at 02:00 (UTC) every day.
  • A continuous archive — every change is saved as it happens, so the database can be rebuilt as it was at any moment, not only at 02:00.

Both are kept for a retention window: 3 days unless you choose otherwise. Retention is a span of time, not a number of backups — with a daily backup, a 3-day window holds two or three base backups and every change between them. Backups are stored in the same region as the database.

Retention window (3 days by default) — anything inside it can be restoredevery change archivedcontinuous archivereplaynowbase backupbase backupbase backupday 1, 02:00day 2, 02:00day 3, 02:00--at 10:15DROP TABLEat 10:16New database: orders-recoveredown credentials · data as at 10:15Original: orderskeeps running, never modified
Base backups plus the continuous archive let you restore to any moment in the retention window, into a new database.

In the console

Open the database and go to the Backups tab.

  • Enable backups turns on the daily backup and the archive with the 3-day window.
  • Back up now takes an extra base backup immediately. It shows as running, then completed (or failed).
  • Delete (the bin icon beside a finished backup) removes that backup for good: it is no longer listed or offered for restore, and its stored files are cleared when the retention window passes. A backup that is still running cannot be deleted. Deleting a backup needs the owner or admin role.
  • Turn off backups asks for confirmation, then stops the daily backup and the archive. Backups already taken are kept and can still be restored — but only to the moment each one completed, not to any point in between. You can still back up by hand, and turn backups on again at any time.

The tab reads whether backups are on from the database itself. If it cannot check just then, it says so and shows neither button rather than guessing. If it warns that scheduled backups are running but continuous archiving is not attached, point-in-time recovery is not available: press Turn archiving back on in the warning, or run cloud db backup enable <name> (both are safe to repeat and keep existing backups).

Turning backups on or off and taking a backup need the member role or above; restoring and deleting a backup need owner or admin. Viewers can see the list.

From the CLI

cloud db backup enable orders                  # daily backup + archive, 3-day window
cloud db backup enable orders --retention 7d   # or 2w, 1m — run again to change the window
cloud db backup create orders                  # take a base backup now
cloud db backup list orders                    # newest first, with status
cloud db backup disable orders                 # stop automatic backups; ones taken are kept

--retention is a whole number followed by d (days), w (weeks) or m (months). The console always uses 3 days; choose a different window from the CLI or API. Turning backups off keeps the backups already taken; the API equivalent is POST …/backups/disable.

Restore

A restore always creates a new database from the backups, and leaves the original running and untouched. If the original is still taking writes, overwriting it would lose exactly the data the restore was meant to save — so you get two databases, with different connection details, and choose when to move your app across.

  1. Open the database you want to recover, go to Backups and click Restore.
  2. Give the new database a name (it suggests <name>-restored).
  3. Optionally pick a point in time in your browser's local time. Leave it blank to restore the most recent state the backups hold. The time must be after the end of the oldest completed backup and not in the future; anything else is refused with the reason (for a time that is too early, the earliest time you can use).
  4. Click Create restored database. You are taken to the new database, which goes through Provisioning to Ready like any other.
  5. Check the data, then point your app at the new database's credentials. Delete the old one when you no longer need it.
cloud db restore orders --to orders-recovered --wait
cloud db restore orders --to orders-at-ten --at 2026-09-04T10:15:00Z --wait

--at takes an RFC 3339 time, for example 2026-09-04T10:15:00Z (UTC) or 2026-09-04T12:15:00+02:00 (Lusaka time). It must fall inside the retention window — no earlier than the end of the oldest completed backup, and not in the future — and continuous archiving must have been on at that moment. The console, CLI and API apply the same check; the API answers 400 with the reason.

What the new database gets:

  • The same PostgreSQL version and plan as the original, in the same region.
  • The data as it was at the chosen moment. Copy its connection details from its own Credentials tab once it is Ready; its public hostname and private name are different.
  • A description noting which database and time it was restored from.
  • Backups off. Turn them on for the new database if you are going to keep it.

Restoring needs the owner or admin role — in the console as well as the CLI and API — and a funded, unsuspended organisation, because it creates a database the organisation pays for. The new name follows the usual rule (up to 40 lowercase letters, digits and hyphens). Restore needs backups: if a database has never had them turned on, there is nothing to restore from.

Billing

Each plan has an hourly price in Kwacha, shown on the create page and on pricing. Every hour, your organisation's wallet is charged one hour of the plan for each database that is Ready, Degraded, Starting, Restarting or Stopped.

  • Not charged: while Provisioning, after a create that Failed, while Suspended, and once deleted.
  • Stopped is charged the same as Ready, because the database keeps its disk and plan.
  • Plan upgrades take the new price from the next hour.
  • Backups have no separate charge today.
  • A restore creates another billed database.

If the wallet reaches zero, the organisation gets a grace period (72 hours) in which everything keeps running. After that it is suspended: every database that would otherwise be charged — Ready, Degraded, Starting or Restarting — is shut down and marked Suspended, and stops being charged; one you had stopped yourself stays Stopped and is not charged while suspended. Topping up brings the suspended ones back to Ready; databases you had stopped yourself stay stopped. Data in a suspended organisation is deleted after 21 days. See Billing for the details.

Limits

  • Databases per organisation: 10 by default. Contact support if you need more.
  • Engine: PostgreSQL only, at the versions in the catalogue.
  • One server per database, no standby or read replicas, up to 100 connections.
  • Disk: the plan's size. It grows only by upgrading the plan.
  • Plans: upgrades only, from the console, by an owner or admin.
  • Names: unique among the organisation's live databases; lowercase letters, digits and hyphens, up to 40 characters.
  • Access: no superuser and no server-level configuration changes.
  • Region: zm-lsk-1 (Lusaka). A database and its backups stay in its region.

Delete

In the console, open the database and click Delete (or use the row menu on the Databases list), then type the database's name to confirm. From the CLI:

cloud db delete orders          # asks you to type the name
cloud db delete orders --yes    # no prompt, for scripts

Deleting needs the owner or admin role. It withdraws the public hostname and private name, removes the server and its disk, and removes every backup with it. Billing stops, and the name is free to use again immediately.

Troubleshooting

The connection is closed or times out before login

  • Check TLS is on: sslmode=require in the URI, or PGSSLMODE=require. Without TLS the public endpoint cannot tell which database you want.
  • Check your client sends the hostname in the TLS handshake. With psql or libpq, use version 14 or later and do not set sslsni=0. With other drivers, update to a current release.
  • Use the hostname, not an IP address — the hostname is how your database is found.
  • Check the database is Ready, not Stopped or Suspended.
  • Check your network allows outbound connections to port 5432.

Certificate verification failed

You are using verify-ca or verify-full (or a driver that verifies by default). Use sslmode=require, or turn off certificate verification while keeping SSL on.

Password authentication failed

Copy the credentials again from the Credentials tab or cloud db credentials. The user and database are both app, not your database's name. If you restored, the new database has its own hostname — check you are connecting to the one you mean.

The private name (db-…) does not resolve

It only works from apps in the same organisation. From anywhere else — including virtual machines — use the public hostname with TLS.

Too many connections

The server accepts up to 100. Use a connection pool and close connections you no longer need; a restart clears connections left behind by a crashed client.

Credentials are not available yet

The database is still Provisioning, Starting or Degraded. Wait for Ready. If it stays in Provisioning for more than 15 minutes it is marked Failed: delete it and create it again.

An error ends in "Reference ABC123…"

Something failed on the platform's side. Contact support with the reference; it lets them find the full details.

CLI and API reference

Every command takes the database's name as its first argument. Add -o json or -o yaml for machine-readable output. See the CLI guide for installing, signing in and choosing an organisation.

OperationCLIAPI (/api/v1/orgs/<org>/databases…)
Versionscloud db enginesGET /api/v1/database-engines
Listcloud db listGET …/databases
Createcloud db create NAME --engine postgresqlPOST …/databases
Showcloud db get NAMEGET …/databases/NAME
Credentialscloud db credentials NAMEGET …/NAME/credentials
Start / stop / restartcloud db start|stop|restart NAMEPOST …/NAME/start|stop|restart
Enable backupscloud db backup enable NAMEPOST …/NAME/backups/enable
Disable backupscloud db backup disable NAMEPOST …/NAME/backups/disable
Back up nowcloud db backup create NAMEPOST …/NAME/backups
List backupscloud db backup list NAMEGET …/NAME/backups
Restorecloud db restore NAME --to NEWPOST …/NAME/restore
Deletecloud db delete NAMEDELETE …/databases/NAME
Change plan— (console)— (console)

API requests authenticate with an API token as a bearer token, and organisations are addressed by slug or id. Create takes name, engine (postgresql), region, and optionally version, size and description; it answers 201 with the database in provisioning. Start, stop and restart answer 202 — poll GET …/databases/NAME until the status settles. Restore takes name and an optional at; backups/enable takes an optional retention. The full schema is published at /api/v1/openapi.json.