Skip to content

kupe database command reference

kupe database, or kupe db, creates, inspects, resizes and deletes PostgreSQL databases and binds them into namespaces of your clusters. The CLI never shows a password. For the model behind these commands, see Databases. To install the CLI, see Kupe CLI.

--cpu, --memory and --storage are per instance. A database with high availability runs two instances, each requesting those resources.

Every command also takes the CLI’s global flags (--tenant, --context, --config, --token, --api-url, --quiet, --verbose, --no-color).

Command Does
list List databases in the tenant
get NAME Show one database
create NAME Create a database and wait for it to run
update NAME Resize, or change high availability or deletion protection
delete NAME Delete a database and its data
rotate-credentials NAME Rotate one role’s password
wait NAME Block until a database reaches a phase
binding add DATABASE Bind a role into namespaces of a cluster
binding list DATABASE List a database’s bindings
binding get DATABASE BINDING Show one binding
binding update DATABASE BINDING Add or remove a binding’s namespaces
binding delete DATABASE BINDING Remove a binding
kupe database list [--phase PHASE] [-o table|wide|json|yaml|name|go-template=...]
NAME ENGINE VERSION RUNNING PHASE HA AGE
orders-db postgres 18 18.6 Running off 3d

VERSION is the major you asked for and RUNNING the release the database runs. -o wide adds CPU, MEMORY, STORAGE, INSTANCES (ready/desired) and PROTECTED.

--phase shows only the databases in one phase: pending, provisioning, running, upgrading, degraded or terminating, in any case. The CLI filters the list after the API returns it. Any other value exits 2.

Terminal window
kupe db list --phase degraded
kupe database get NAME [-o table|json|yaml|go-template=...]
Name: orders-db
Engine: postgres
Version: 18 (running 18.6)
Phase: Running
High Availability: off
Instances: 1/1
CPU: 500m
Memory: 1Gi
Storage: 10Gi
Deletion Protection: on
Created: 2026-10-09T10:00:00Z (3d ago)

Exits 4 if the database does not exist.

kupe database create NAME --cpu CPU --memory MEMORY --storage STORAGE [flags]
Flag Default Description
--cpu required CPU per instance, without a unit: 500m, 2, 0.5.
--memory required Memory per instance, with a unit: 1Gi, 512Mi.
--storage required Storage per instance, with a unit: 10Gi. Grows later, never shrinks.
--version platform default Major version, for example 18.
--engine postgres The only engine.
--ha off Two instances with a synchronous standby.
--no-deletion-protection off Create without deletion protection, which is otherwise on.
--wait true Wait for Running. --wait=false returns once the request is accepted.
--wait-timeout 30m Give up waiting after this long.
-o, --output table table, json, yaml or go-template=...
Terminal window
# The smallest database
kupe database create orders-db --cpu 250m --memory 512Mi --storage 5Gi
# Highly available, a specific major, without waiting
kupe db create orders-db --version 17 --cpu 1 --memory 2Gi --storage 20Gi --ha --wait=false

The API enforces the platform minimum and maximum sizes and your plan’s limits, and the CLI prints its message as returned.

Exit Meaning
0 The database is Running, or the request was accepted with --wait=false
1 The database entered Degraded
2 Missing or malformed flags, or the API rejected the request
5 The name already exists, or a common refusal such as the plan’s database limit
7 Database creation is paused, the API is temporarily unavailable, or the CLI lost contact with the API while waiting
8 --wait-timeout expired
130 Interrupted while waiting. Creation continues
kupe database update NAME [--cpu CPU] [--memory MEMORY] [--storage STORAGE] [--ha|--ha=false] [--deletion-protection|--deletion-protection=false] [--wait=false] [--wait-timeout DURATION] [-o FORMAT]
Flag Default Description
--cpu unchanged New CPU per instance, without a unit.
--memory unchanged New memory per instance, with a unit.
--storage unchanged New storage per instance, with a unit. Grows only.
--ha unchanged --ha turns high availability on and --ha=false turns it off.
--deletion-protection unchanged --deletion-protection turns it on and --deletion-protection=false turns it off.
--wait true Wait until the change is in effect. --wait=false returns once it is accepted.
--wait-timeout 30m Give up waiting after this long.
-o, --output table table, json, yaml or go-template=...

Only the flags you pass are changed, and at least one is required. CPU and memory changes restart the instances one at a time (switching over first with high availability), and storage grows online. The major version cannot be changed.

Terminal window
kupe database update orders-db --cpu 1 --memory 2Gi
kupe db update orders-db --storage 20Gi
kupe db update orders-db --ha
kupe db update orders-db --deletion-protection=false

By default the command waits until the change is in effect, which is when all three of these hold:

  • status.observedGeneration has reached the generation the change produced, so the platform has applied it.
  • The phase is Running.
  • The Ready condition is True, so every instance runs the change. The phase stays Running while the instances roll onto it.

A database with one instance is Degraded while that instance restarts for a CPU or memory change. The wait rides that out, and exits 1 if the database is still Degraded 5 minutes later. Degraded counts only once observedGeneration has caught up, so a status read from before the change never fails the wait. A database that starts being deleted ends the wait at once with exit 1, and a change the platform never applies ends in exit 8 at --wait-timeout.

A change to deletion protection alone is in effect as soon as the API accepts it, because a delete reads the setting when it arrives, so the command does not wait for it. Turning protection off on a Degraded database, or on one that never finished provisioning, returns at once.

A smaller --storage exits 2 with the API’s message, for example storage can only grow: 5Gi is smaller than the current 10Gi. Any other rejected change exits 2 with the API’s message, which names the field and the rule it broke. If someone else changed the database at the same moment, the CLI retries once, then exits 5.

Exit Meaning
0 The change is in effect, or was accepted (with --wait=false, or for deletion protection alone)
1 The database stayed Degraded for 5 minutes after the platform applied the change, or started being deleted
2 Missing or malformed flags (including none at all), or the API rejected the change
4 The database does not exist
5 The database changed again after the retry, or a common refusal
7 The API is temporarily unavailable, or the CLI lost contact with it while waiting
8 --wait-timeout expired. The platform keeps applying the change
130 Interrupted while waiting. The change continues
kupe database delete NAME [--yes] [--wait=false] [--wait-timeout DURATION]
Flag Default Description
-y, --yes off Skip the confirmation prompt. Required without a terminal.
--wait true Wait until the database is gone. --wait=false returns once the delete is accepted.
--wait-timeout 15m Give up waiting after this long.

On a terminal you type the database name to confirm. A database with deletion protection on is refused (exit 5): turn it off first with kupe database update NAME --deletion-protection=false.

Deleting a database removes it immediately, with its Service and credentials Secrets in every bound namespace. Its storage is kept for 7 days for support-assisted recovery, then permanently deleted. There is no self-service restore.

By default the command waits until the database is gone. It is Terminating while its bindings are removed from every cluster namespace and its instances stop, which can take several minutes. Progress and the final database NAME deleted line go to stderr, and nothing is printed on stdout. With --wait=false the command prints database/NAME delete requested on stdout as soon as the delete is accepted. Follow it with kupe database wait NAME --for deleted.

Terminal window
kupe database delete orders-db
kupe db delete orders-db --yes --wait=false
Exit Meaning
0 The database is gone, or the delete was accepted with --wait=false
2 Confirmation missing or not matching
4 The database does not exist
5 Deletion protection is on, or the database kept changing during the delete
7 The API is temporarily unavailable, or the CLI lost contact with it while waiting
8 --wait-timeout expired. Deletion continues
130 Interrupted while waiting. Deletion continues
kupe database rotate-credentials NAME --role owner|readwrite|readonly [--yes]

Requests a new password for one role and, by default, waits until it is in effect: the database uses the new password, so the old one no longer works, and every binding of the role has written it to its credentials Secret in every namespace it lists. Each role has its own password; rotating one leaves the others unchanged. If someone rotates the same role again meanwhile, the command waits for that newer rotation instead. Applications holding the old password must restart. Prompts on a terminal, and --yes is required without one. Prints database/NAME ROLE credentials rotated, or with --wait=false database/NAME ROLE credentials rotation requested. Exits 5 if the database is being deleted, and 8 if the rotation is not in effect by --wait-timeout (default 10m); the platform keeps applying it.

Terminal window
kupe database rotate-credentials orders-db --role readwrite
kupe db rotate-credentials orders-db --role readonly --yes
kupe database wait NAME [--for PHASE] [--timeout DURATION]

--for takes running (the default), pending, provisioning, upgrading, degraded, terminating or deleted. --timeout defaults to 30m.

Exit Meaning
0 The database reached the phase, or is gone with --for deleted
1 The database entered Degraded while waiting for another phase
7 Lost contact with the API while waiting
8 --timeout expired
130 Interrupted while waiting
kupe database binding add DATABASE --cluster CLUSTER --namespace NAMESPACE... --role owner|readwrite|readonly [flags]
Flag Default Description
--cluster required Cluster to bind into. It may not exist yet.
--namespace required Namespace that receives the Service and Secret. Repeat it for each namespace, up to 10.
--role required owner (schema changes), readwrite (reads and writes data) or readonly (reads data).
--secret-name DATABASE-ROLE Name of the credentials Secret in every namespace.
--name DATABASE-CLUSTER-ROLE Binding name. Set it if the default is longer than 63 characters, or for a second binding of the same role into the same cluster.
--wait true Wait until the binding is Ready in every namespace.
--wait-timeout 5m Give up waiting after this long.
-o, --output table table, json, yaml or go-template=...
Terminal window
# Migrations with the owner role, the API and its workers with the readwrite role
kupe database binding add orders-db --cluster prod --namespace orders-migrate --role owner
kupe db binding add orders-db --cluster prod --namespace orders-api --namespace orders-worker --role readwrite
# A reporting job with the readonly role
kupe db binding add orders-db --cluster prod --namespace orders-reports --role readonly

The default name leaves the namespaces out, because they can change. A long database or cluster name can take it past 63 characters (readwrite is the longest role name), and then the command needs --name. A second binding of the same role into the same cluster also needs --name; usually kupe database binding update ... --add-namespace on the first one is what you want, and the hint on the refusal says so.

A binding waits in Pending while its database or cluster does not exist yet or is not running. A wait that runs out then exits 8 with the binding’s message, for example binding "orders-db-prod-readwrite" is still Pending: cluster prod does not exist.

A binding is Failed when one of its namespaces already has a Service or Endpoints object named after the database, or a Secret of the binding’s Secret name, that the platform did not create. The command stops at once:

Error: binding "orders-db-prod-readwrite" failed: a Secret named "orders-db-readwrite" already exists in namespace "orders-api", not created by the platform; remove it, or bind with another secretName

Rename or remove that object and the binding becomes Ready on its own, or delete the binding and add it again with another --secret-name. When the binding lists other namespaces without a clash, the hint also gives the binding update --remove-namespace command that stops binding into the clashing one.

Exit Meaning
0 Ready, or accepted with --wait=false
1 Failed for a reason other than a name clash
2 Missing or malformed flags
4 The database does not exist
5 The binding name exists, or the binding is Failed on a name clash
7 Lost contact with the API while waiting
8 --wait-timeout expired
130 Interrupted while waiting. The binding was created
kupe database binding list DATABASE [-o table|wide|json|yaml|name|go-template=...]

One row per namespace of each binding, with columns NAME, CLUSTER, NAMESPACE, ROLE, SECRET, PHASE and AGE. -o wide adds the namespace’s HOST and MESSAGE. PHASE is Pending for a namespace the platform has not reported yet, and Removing for one taken out of the binding that is still being withdrawn. -o json and -o yaml print the bindings as the API returns them. Exits 4 if the database does not exist.

$ kupe db binding list orders-db
NAME CLUSTER NAMESPACE ROLE SECRET PHASE AGE
orders-db-prod-owner prod orders-migrate owner orders-db-owner Ready 2m
orders-db-prod-readwrite prod orders-api readwrite orders-db-readwrite Ready 2m
orders-db-prod-readwrite prod orders-worker readwrite orders-db-readwrite Pending 2m
orders-db-prod-readonly prod orders-reports readonly orders-db-readonly Ready 1m
kupe database binding get DATABASE BINDING [-o table|json|yaml|go-template=...]

Shows the binding and its Secret name, then a table of its namespaces with each one’s phase, in-cluster host and message. A binding that belongs to another database exits 4.

kupe database binding update DATABASE BINDING [--add-namespace NAMESPACE]... [--remove-namespace NAMESPACE]... [flags]
Flag Default Description
--add-namespace none Namespace to add. Repeat it for each namespace.
--remove-namespace none Namespace to remove. Repeat it for each namespace.
--wait true Wait until every listed namespace is Ready and every removed one is withdrawn.
--wait-timeout 5m Give up waiting after this long.
-o, --output table table, json, yaml or go-template=...
Terminal window
kupe database binding update orders-db orders-db-prod-readwrite --add-namespace orders-worker
kupe db binding update orders-db orders-db-prod-readwrite --remove-namespace orders-worker

A namespace added gets the Service and the Secret. A namespace removed loses them and its network path to the database, while the other namespaces keep working; the Service and the network path stay if another binding of the same database lists that namespace. The binding’s cluster, role and Secret name cannot change.

The command reads the binding, edits its namespace list and writes it back. If the binding changed in between, it reads it again and retries once. Adding a namespace that is already listed, or removing one that is not, changes nothing: the command then sends no request and says so on stderr.

A binding keeps 1 to 10 namespaces, so a change that would leave none (delete the binding instead) or more than 10 exits 2 before a request is sent. The platform checks the namespaces added as it does for binding add, and its message names the rule a change breaks: the namespace is already bound with the same role in that cluster, the Secret name is already used there, or the database’s bindings would list more than 20 namespaces. A change that only removes namespaces passes those checks.

Removing a namespace does not change the role’s password: rotate it with kupe database rotate-credentials if that namespace should also lose knowledge of it.

Exit Meaning
0 Ready in every listed namespace and every removed one withdrawn, or accepted with --wait=false
1 A listed namespace is Failed for a reason other than a name clash
2 Missing or contradictory flags, a list that would be empty or over 10, or a change the platform refused
4 The database or the binding does not exist
5 The binding or its database is being deleted, the binding kept changing, or a listed namespace is Failed on a name clash
7 Lost contact with the API while waiting
8 --wait-timeout expired
130 Interrupted while waiting. The change was accepted
kupe database binding delete DATABASE BINDING [--yes]

Removes the Secret from every namespace the binding lists, and the Service and the network path from each one no other binding of the database lists. The role’s password does not change: rotate it with kupe database rotate-credentials if those namespaces should also lose knowledge of it. To remove only some of the namespaces, use kupe database binding update --remove-namespace. Prompts on a terminal, and --yes is required without one. The removal happens in the background and the command prints binding/NAME delete requested.

Every kupe database command maps these refusals the same way. The error text is the API’s message, and the hint under it is the next step.

Refusal Exit Next step
Database creation is paused (creation_paused) 7 Retry later, and check status.kupe.cloud
The database is being deleted (terminating) 5 kupe database wait NAME --for deleted
The tenant is being deleted (tenant_terminating) 5 None
The tenant is suspended (tenant_suspended) 5 Contact support
The plan’s database limit is reached (plan_limit_reached) 5 Delete a database or upgrade the plan
The API is temporarily unavailable (HTTP 503) 7 Retry in a few seconds

The tenant refusals apply to growing a database (more CPU, memory or storage, or high availability turned on) as well as to creating one. The CLI never retries a create or an update on its own, so after a 503 the script or the person running it decides whether to retry.