The module lifecycle stagePreview
The module has requirements for installation

Postgres

Namespaced resource that allows creating the final configuration and serves as the source of truth for the state of a specific deployed postgres service.

PostgresClass Name

The name of the class to which a specific resource will be linked. Deploying the service is impossible without a created PostgresClass.

spec:
  postgresClassName: default

Instance

Section that describes the resources of the service being created. Must pass validation according to the sizingPolicy of the corresponding class:

spec:
  instance:
    memory:
      size: 1Gi
    cpu:
      cores: 1
      coreFraction: 50
    persistentVolumeClaim:
      size: 1Gi
      storageClassName: default

Configuration

Section describing the configuration of the postgres service. Must pass validation according to the overridableConfiguration and validations of the corresponding class:

spec:
  configuration:
    maxConnections: 300
    sharedBuffers: 128Mi

Type

Service replication type, can be Cluster or Standalone

spec:
  type: Cluster

Cluster

Section describing the topology and replication method of the postgres cluster. Must pass validation according to the allowedTopology of the corresponding class:

spec:
  cluster:
    topology: Zonal
    replication: ConsistencyAndAvailability

Replication modes and operational trade-offs

Each replication value maps to a fixed number of instances and specific PostgreSQL settings. Here is what matters most when choosing a mode.

Availability (2 instances: primary + asynchronous replica)

Optimized for fast recovery after a failure (RTO < 60 secs).

  • Replication is asynchronous (synchronous_commit = local): the primary acknowledges a transaction as soon as it writes to its own WAL, without waiting for the replica. If the primary crashes right now, the last few transactions may not have reached the replica yet — those writes will be lost. RPO is not zero.

  • Replica readiness is checked by asking PostgreSQL whether it accepts connections (pg_isready). A replica can be marked ready even if it is significantly behind the primary (i.e. replication lag is not taken into account).

When to choose: availability matters more than zero data loss; your workload can tolerate a minor data divergence.

Consistency (2 instances: primary + synchronous replica)

Optimized for zero loss of committed transactions (RPO = 0, RTO < 120 secs).

Average failover time is approximately 60 seconds.

  • Replication is synchronous (synchronous_commit = remote_apply): the primary acknowledges a transaction only after the replica has written the WAL to disk and apply transaction. No committed data is lost on failover (RPO = 0), but each commit takes slightly longer.

  • Writes can stall completely. If the replica is unavailable, the primary stops accepting commits until the replica recovers.

  • Replica readiness is based on replication stream state (streaming): the replica is considered ready only while it is actively receiving WAL from the primary.

When to choose: losing even one acknowledged transaction is unacceptable, and you can accept writes stopping when the replica is lost.

ConsistencyAndAvailability (3 instances: primary + one synchronous + one asynchronous replica)

A balance between durability and availability. Recommended for production workloads (RPO ~ 0, RTO < 90 secs).

Average failover time is approximately 30 seconds.

  • Three instances require more resources (3 pods, 3 PVCs), but improve reliability: the third node allows the operator to make a well-informed failover decision (quorum).

  • Replication in remote_write mode: the primary acknowledges a transaction once the synchronous replica has received the WAL and flushes it to disk. This is faster than remote_apply in Consistency mode but stricter than purely asynchronous replication (as in Availability mode) — by the time the primary fails, most or all data is already at the replica.

  • Informed automated failover: with three nodes (quorum) the operator can verify that the synchronous replica is sufficiently up to date before promoting it. This reduces the risk of promoting a node with stale data, though in complex failure scenarios failover may take slightly longer.

  • Synchronous replica: Selected automatically and not fixed (i.e., the first replica to write the WAL to disk and notify the primary becomes the synchronous replica). During failover, it becomes the primary almost instantly after receiving the promote request.

  • The asynchronous replica can lag behind the synchronous one. Do not rely on it for up-to-date reads or as a failover candidate — it is a standby node only.

When to choose: production workload where both data durability and fast automatic recovery are important.

Supported PostgreSQL Versions

Only supported version is 17.6.

Our images for running PostgreSQL containers are based on distroless architecture.

Key concepts of each image:

  • Russian locale ru_RU.UTF-8 has been added
  • postgres processes are run under a user with UID 64535

Users

Section that allows managing postgresql users. The list of structures declaratively reflects the state of service users and with the help of the Postgres Operator controller, will be synchronized in the Reconcile loop. Warning: Removing a list element or changing a name will mean deleting the user in the postgresql service.

spec:
  users:
    - name: test-rw
      hashedPassword: >-
        SCRAM-SHA-256$4096:8LTjDsWOlQ7fnvr0DqRQx0TXMTh6LIyQJow2UnNlsJE=$ZjQi5diDTvn0g7is1ez9qPSGm6SoGezF0FVCZXssDKw=:IEzN8Dz5KcGd1r47thky5XFRhXlIMeoNLNfZtIlGv/8=
      role: rw
#      password: 123
#      storeCredsToSecret: test-rw-creds

User Passwords

For security purposes, by default we use hashed passwords for database access. You can specify a password by yourself in the hashedPassword field in MD5/SCRAM-SHA-256 format, which is natively supported by postgresql. Or specify a plain-text password in the password field and we will automatically mutate it to hashed format. In case you specified plain-text password, you can specify the name of a secret, which will be created to store the password in plain text there, and updated with connection strings for existing logical databases.

Password change occurs by changing one of the fields hashedPassword or password followed by its mutation.

Warning: It is possible to specify a password either in hashed form hashedPassword, or as a plain string password. Warning: Delivery of credentials, when the storeCredsToSecret field is not specified, is the user’s responsibility.

Example user secret fields:

Kind: Secret
name: test-rw-creds
data:
  host: 'd8ms-pg-test-rw'
  password: '123'
  test-dsn: 'host=d8ms-pg-test-rw;port=5432;dbname=test;user=test-ro;password=123'
  username: test-rw

Roles

To manage user roles, we offer a set of pre-created roles:

  • rw - role that allows reading, writing, changing table schema within a logical database. Owns all logical databases.
  • ro - role that only allows reading from logical database tables.
  • monitoring - allows collecting metrics from system tables, corresponds to the built-in pg_monitor role.

Pre-created roles work globally for all logical databases within one deployed service.

Databases

Section that allows managing postgresql logical databases. The list of structures declaratively reflects the state of service logical databases and with the help of the Postgres Operator controller, will be synchronized in the Reconcile loop. Warning: Removing a list element or changing a name will mean deleting the database and ALL DATA of this database in the postgres service.

spec:
  databases:
  - name: "test"

Status

The status of the Managed Postgres service is reflected in the Postgres resource. The Conditions structure clearly shows the current status of the service

Significant types:

  • LastValidConfigurationApplied - Aggregating type that shows whether the last valid configuration was successfully applied at least once.
  • ConfigurationValid - shows whether the configuration passed all validations of the associated PostgresClass.
  • ScaledToLastValidConfiguration - shows whether the number of running replicas matches the specified configuration.
  • Available - shows whether the master replica of the service is running and accepting connections.
  • UsersSynced - shows whether all users have been synchronized and brought to the described state.
  • DatabasesSynced - shows whether all logical databases have been synchronized and brought to the described state.
conditions:
    - lastTransitionTime: '2025-09-22T23:20:36Z'
      observedGeneration: 2
      status: 'True'
      type: Available
    - lastTransitionTime: '2025-09-22T14:38:04Z'
      observedGeneration: 2
      status: 'True'
      type: ConfigurationValid
    - lastTransitionTime: '2025-09-22T14:38:06Z'
      observedGeneration: 2
      status: 'True'
      type: DatabasesSynced
    - lastTransitionTime: '2025-09-22T14:38:47Z'
      observedGeneration: 2
      status: 'True'
      type: LastValidConfigurationApplied
    - lastTransitionTime: '2025-09-22T23:20:36Z'
      observedGeneration: 2
      status: 'True'
      type: ScaledToLastValidConfiguration
    - lastTransitionTime: '2025-09-22T14:38:05Z'
      observedGeneration: 2
      status: 'True'
      type: UsersSynced

Status False indicates a problem at one stage or another or incomplete state synchronization. For such a state, reason and message with description will be specified.

---
    - lastTransitionTime: '2025-09-23T14:53:33Z'
      message: Syncing
      observedGeneration: 1
      reason: Syncing
      status: 'False'
      type: LastValidConfigurationApplied
    - lastTransitionTime: '2025-09-23T14:54:58Z'
      message: Not all the instances are running still waiting for 1 to become ready
      observedGeneration: 1
      reason: ScalingInProgress
      status: 'False'
      type: ScaledToLastValidConfiguration
---

Usage Examples

Basic Usage

  1. Create a namespace named postgres.
  2. Create a Postgres resource
kubectl apply -f managed-services_v1alpha1_postgres.yaml -n postgres
apiVersion: managed-services.deckhouse.io/v1alpha1
kind: Postgres
metadata:
  labels:
    app.kubernetes.io/name: managed-psql-operator
  name: test
spec:
  users:
    - name: test-rw
      password: '123'
      role: rw
  databases:
    - name: "testdb"
  postgresClassName: default
  instance:
    memory:
      size: 4Gi
    cpu:
      cores: 2
      coreFraction: 50
    persistentVolumeClaim:
      size: 10Gi
  type: Cluster
  cluster:
    topology: TransZonal
    replication: ConsistencyAndAvailability
  1. Wait until the cluster is created and all conditions are True:
  kubectl get postgres test -n postgres -o wide -w
  1. To connect, use the psql client and the d8ms-test-rw service
  psql -U test-rw -d testdb -h d8ms-test-rw.postgres.svc -p 5432

Deployment in Standalone Mode

  1. Create a namespace named postgres.
  2. Create a Postgres resource
kubectl apply -f managed-services_v1alpha1_postgres.yaml -n postgres
apiVersion: managed-services.deckhouse.io/v1alpha1
kind: Postgres
metadata:
  labels:
    app.kubernetes.io/name: managed-psql-operator
  name: standalone
spec:
  users:
    - name: test-rw
      password: '123'
      role: rw
  databases:
    - name: "testdb"
  postgresClassName: default
  instance:
    memory:
      size: 4Gi
    cpu:
      cores: 2
      coreFraction: 50
    persistentVolumeClaim:
      size: 10Gi
  type: Standalone
  1. Wait until the cluster is created and all conditions are True:
  kubectl get postgres test -n postgres -o wide -w
  1. To connect, use the psql client and the d8ms-pg-standalone-rw service
  psql -U test-rw -d testdb -h d8ms-pg-standalone-rw.postgres.svc -p 5432

Availability for Use by Another Service in k8s

  1. Create a namespace named postgres.
  2. Create a Postgres resource
kubectl apply -f managed-services_v1alpha1_postgres.yaml -n postgres
apiVersion: managed-services.deckhouse.io/v1alpha1
kind: Postgres
metadata:
  labels:
    app.kubernetes.io/name: managed-psql-operator
  name: availability
spec:
  users:
    - name: test-rw
      password: '123'
      storeCredsToSecret: 'test-rw-creds'
      role: rw
  databases:
    - name: "testdb"
  postgresClassName: default
  instance:
    memory:
      size: 4Gi
    cpu:
      cores: 2
      coreFraction: 50
    persistentVolumeClaim:
      size: 10Gi
  type: Cluster
  cluster:
    topology: TransZonal
    replication: ConsistencyAndAvailability
  1. Wait until the cluster is created:
  kubectl get postgres availability -n postgres -o wide -w
  1. To get the DSN for connecting to the database, use the following command. Or set it as an environment variable in the service pod.
kubectl get secret test-rw-creds -n postgres-ek -o jsonpath='{.data.test-dsn}' | base64 --decode