Kvindo™ Cloud Docs
A PostgreSQL Cluster is a managed, horizontally-shardable PostgreSQL deployment built on Citus (distributed PostgreSQL) and Patroni (automated failover), coordinated by a dedicated etcd quorum the platform provisions and manages for you — it never appears in the spec. The cluster is one resource with an array of named shard groups in spec.shardGroups; exactly one of them, marked isCoordinator: true, is the coordinator and holds the cluster's distributed-table metadata.
• Shard groups, not shards — each entry in spec.shardGroups is a full Patroni group (one primary + spec.replicasPerShardGroup replicas), not a single row range; Citus shards individual distributed tables across whichever shard groups exist
• Exactly one shard group is the coordinator, marked explicitly — set isCoordinator: true on exactly one entry, on every create and every modify; it is never inferred from array order. It can never be removed or handed off to a different shard group, but — unlike every other shard group, whose name is its real identity — its own name can be changed freely, since the server resolves that one entry by the flag instead
• Shard groups can't be swapped in one modify — adding a new shard group and removing an old one in the same request is rejected (it's indistinguishable from renaming one you meant to keep); do it as two separate modifies when you genuinely want to replace one
• Scaling is one shard group per modify — the shard-group count may change by at most one in a single request, same discipline as Valkey's own shard scaling
• No root password in the spec — this resource has no user-facing superuser credential. Create a PostgreSqlUser (see below) to get an actual login; the internal bootstrap credential the platform generates for its own automation is never exposed through the API
• Point-in-time restore — spec.restoreConfiguration creates a brand-new cluster restored from another cluster's backups as of a specific UTC moment; immutable, create-time only, and the two clusters are otherwise independent. The source cluster's PostgreSqlDatabase and PostgreSqlUser resources are cloned onto the newly-restored target cluster. The target must have the same shard-group count as the source, a volume size greater than or equal to the source's, and the same PostgreSQL major version as the source
• Public access — private by default; set spec.createPublicIpv4 for a public IPv4 per node, and spec.tlsMode to require to force encrypted connections (mandatory once public access is on)
Citus distributes tables by a chosen distribution column, and that choice shapes what queries stay fast. This is not a drop-in replacement for a single-node database once more than one shard group is in play — plan the schema around it:
• Primary keys must include the distribution column — Citus cannot enforce global uniqueness on any other column
• Foreign keys between distributed tables must reference the same distribution column, or the reference has to go through a reference table (a small table copied to every shard group instead of sharded)
• Joins and transactions that span rows with different distribution-column values cross shard groups — still correct, but noticeably slower than a same-node query, since it involves a real network round trip between shard groups
• Pick the distribution column deliberately — most applications shard on a tenant/customer/organization id, so that a single tenant's data (and its queries) stay within one shard group
None of this applies to a single-shard-group deployment — it behaves like ordinary PostgreSQL with Citus installed but never invoked.
Citus is licensed under the GNU Affero General Public License v3 (AGPLv3). Kvindo Cloud runs the unmodified upstream extension; its complete corresponding source code is publicly available from the upstream project at github.com/citusdata/citus, satisfying the AGPL's network-use source-availability requirement.
Every node runs the same four listeners in front of its local PostgreSQL. The status endpoint fields below each go through a different one — pick the field for what you're actually trying to do, not just whichever one is closest to hand:
• Port 5432 — raw PostgreSQL, direct to that one node. Bypasses HAProxy and the connection pooler entirely — no write-routing, no read load-balancing, no TLS-terminated pooled connections. This is what shardGroups[].primaryEndpoint/replicaEndpoints return: a specific node, not the cluster
• Port 8432 — HAProxy, write, routed to whichever node is the current leader of that shard group. This is what status.connectionUri, status.coordinatorEndpoint, and every entry in status.primaryEndpoints actually use — connect to any of them and Citus's own metadata routes a write or a distributed query to the correct shard group
• Port 7432 — HAProxy, read, load-balanced across that shard group's replicas (the primary is only used as a fallback). This is what every entry in status.readEndpoints uses
• Port 6432 — Odyssey, a TLS-terminating connection pooler in front of 5432. This is the intended entry point for a public-facing or high-connection-count client, and it's reachable on every node once spec.createPublicIpv4/spec.tlsMode are set — but no status field reports it today. Until that's surfaced, derive it yourself: take the host from any endpoint above and connect to port 6432 on it directly
status.port is a fixed default (5432) that is never actually reassigned from any
live node — it does not describe the port any of the endpoint fields above use, including its
own neighbor status.connectionUri (8432). Read the host:port pairs in the endpoint
fields themselves rather than combining a host from one field with status.port.
| UI Name | API / kc name | Terraform name | Optional | Format | Default | Is read only | Description |
|---|---|---|---|---|---|---|---|
| Shard groups | spec.shardGroups |
spec.shard_groups |
No | list of objects | — | No | One entry per shard group — exactly one must have isCoordinator: true; that one can never be removed or handed off to a different shard group |
| Shard group name | shardGroups[].name |
shard_groups[].name |
No | string | — | No | For a non-coordinator entry, this is the real identity — the server resolves it against this cluster's existing shard groups by name and reuses the same underlying group when it matches; renaming is treated as removing the old one and adding a new one, so don't rename one you want to keep. The coordinator entry is the one exception: it's resolved by isCoordinator instead, so its name can be changed freely |
| VPC subnet | shardGroups[].vpcSubnetId |
shard_groups[].vpc_subnet_id |
No | ULID — references a VPC Subnet | — | No | Subnet for this shard group's instances (immutable after creation) |
| Is coordinator | shardGroups[].isCoordinator |
shard_groups[].is_coordinator |
No | boolean | false | No | Exactly one shard group per cluster must set this, on every create and every modify. Immutable once assigned — the coordinator can be renamed but never removed or replaced by a different shard group |
| Replicas per shard group | spec.replicasPerShardGroup |
spec.replicas_per_shard_group |
No | integer | — | No | Extra VMs per shard group replicating the primary for failover |
| Parameters set | spec.postgreSqlParametersSetId |
spec.postgre_sql_parameters_set_id |
Yes | ULID — references a PostgreSqlParametersSet | — | No | postgresql.conf tuning applied cluster-wide |
| Version | spec.version |
spec.version |
Yes | string — PostgreSQL major version | the only currently-accepted value | No | Only one (PostgreSQL, Citus) version pair is currently accepted — PostgreSQL 17.11 with Citus 14.2; no other value works. The version is immutable for the cluster's lifetime once set |
| Backup retention | spec.backupRetentionDays |
spec.backup_retention_days |
No | integer — days | — | No | How long pgBackRest archives are kept in S3 |
| Restore configuration | spec.restoreConfiguration |
spec.restore_configuration |
Yes | object | — | No | Point-in-time restore from another cluster's backups — immutable, create-time only. The source cluster's PostgreSqlDatabase and PostgreSqlUser resources are cloned onto the restored target |
| Source cluster | restoreConfiguration.postgreSqlId |
restore_configuration.postgre_sql_id |
No | ULID — references a PostgreSql cluster | — | No | The cluster whose backups to restore from. The target cluster must have the same shard-group count as this source cluster, a volume size greater than or equal to it, and the same PostgreSQL major version |
| Restore time | restoreConfiguration.restoreTime |
restore_configuration.restore_time |
No | string — UTC timestamp | — | No | Data after this moment is not present in the restored cluster |
| VM offer | spec.vmOfferId |
spec.vm_offer_id |
No | string (≤32) — compute offer | — | No | Applies to every instance across every shard group; see the Console catalog |
| Volume offer | spec.volumeOfferId |
spec.volume_offer_id |
No | string (≤32) — disk offer | — | No | Applies to every instance |
| Volume size | spec.volumeSizeGiB |
spec.volume_size_gib |
No | integer — GiB | — | No | Applies to every instance (immutable after creation) |
| TLS mode | spec.tlsMode |
spec.tls_mode |
Yes | string — allow/require |
allow |
No | require is mandatory once spec.createPublicIpv4 is set |
| Public IPv4 | spec.createPublicIpv4 |
spec.create_public_ipv4 |
Yes | boolean | false |
No | Give every node a public IPv4 (immutable after creation) |
| Port | status.port |
status.port |
— | integer | — | Yes | A fixed default (5432), never reassigned from any live node — it does not describe the port any endpoint field on this page actually uses. See Connecting: ports and pooling above |
| Connection URI | status.connectionUri |
status.connection_uri |
— | string | — | Yes | A starting point — a real client still needs to route reads/writes appropriately. Uses HAProxy's write port (8432), same as coordinatorEndpoint/primaryEndpoints |
| Coordinator endpoint | status.coordinatorEndpoint |
status.coordinator_endpoint |
— | string | — | Yes | host:port (HAProxy write, 8432) of the coordinator shard group's current primary |
| Primary endpoints | status.primaryEndpoints |
status.primary_endpoints |
— | list of strings | — | Yes | Every node's write entry point (HAProxy write, 8432) — the coordinator, every other shard group's primary, and every replica, not just the shard-group leaders. Connect to any entry: each node runs its own HAProxy and every node's Citus metadata is kept in sync, so any of them routes a write to the correct shard group's current leader and can plan/execute a distributed query across the whole cluster |
| Read endpoints | status.readEndpoints |
status.read_endpoints |
— | list of strings | — | Yes | Every node's read entry point (HAProxy read, 7432, load-balanced across that shard group's replicas). Offloads only plain (non-Citus) tables to a replica — distributed and reference tables are still served by the shard's primary regardless of which node you connect to, because Patroni standbys aren't registered as Citus secondary nodes yet. Check citusManagedTableCounts to see how much of your schema is actually being offloaded |
| Citus managed table counts | status.citusManagedTableCounts |
status.citus_managed_table_counts |
— | map of string to integer | — | Yes | How many tables of each Citus type exist — keyed by type (distributed, reference, ...), not by individual table name. This is the read endpoint's own honesty check: a non-empty value here means some of your tables are NOT being offloaded to a replica |
| Cluster state | status.clusterState |
status.cluster_state |
— | string | — | Yes | Reported directly from the underlying probe |
| Anti-affinity OK | status.antiAffinityOk |
status.anti_affinity_ok |
— | boolean | — | Yes | False when a primary and one of its own replicas share a VM (degraded placement) |
| Anti-affinity message | status.antiAffinityMessage |
status.anti_affinity_message |
— | string | — | Yes | Human-readable detail behind antiAffinityOk |
| Shard groups | status.shardGroups |
status.shard_groups |
— | list of objects | — | Yes | Observed state per shard group |
| Is coordinator | shardGroups[].isCoordinator |
shard_groups[].is_coordinator |
— | boolean | — | Yes | The observed truth from the live cluster, not an echo of the spec field of the same name — reports whether this shard group is genuinely running as Citus's coordinator group right now |
| Primary endpoint | shardGroups[].primaryEndpoint |
shard_groups[].primary_endpoint |
— | string | — | Yes | host:port of this shard group's current primary — raw PostgreSQL (5432), a specific node, bypassing HAProxy and the connection pooler |
| Replica endpoints | shardGroups[].replicaEndpoints |
shard_groups[].replica_endpoints |
— | list of strings | — | Yes | host:port for each of this shard group's replicas — raw PostgreSQL (5432), same bypass as primaryEndpoint above |
| Nodes | status.nodes |
status.nodes |
— | list of objects | — | Yes | One entry per provisioned instance across all shard groups |
| Private IPv4 | nodes[].privateIpV4 |
nodes[].private_ipv4 |
— | string | — | Yes | Address on the shard group's VPC subnet |
| Is primary | nodes[].isPrimary |
nodes[].is_primary |
— | boolean | — | Yes | Whether this instance is currently its shard group's primary |
| Patroni state | nodes[].patroniState |
nodes[].patroni_state |
— | string | — | Yes | Raw Patroni member state |
A private 2-shard-group cluster (real Citus sharding) with one replica per shard group, on an existing subnet.
Terraform
Python SDK
kc CLI
Resource docs
Examples