New to KubeDB? Please start with the KubeDB documentation.

Deploy a TDE Encrypted Postgres

This guide shows how to run a KubeDB Postgres with Transparent Data Encryption (TDE) backed by HashiCorp Vault, verify that data is encrypted at rest, rotate the principal key, and enable WAL encryption.

Read the overview first.

Before You Begin

  • A Kubernetes cluster with KubeDB installed.
  • A reachable HashiCorp Vault (or KMIP server). This guide uses Vault.
  • A PostgresVersion with spec.distribution: Percona and spec.tde.supported: true. Confirm one is available:
$ kubectl get postgresversion 17.9-percona -o jsonpath='{.spec.distribution} tde={.spec.tde.supported}{"\n"}'
Percona tde=true

All commands use the demo namespace:

$ kubectl create ns demo
namespace/demo created

Provide the KMS credentials

Create the Secret holding the Vault token (key token). The operator projects it into every pod at /etc/pg-tde/vault.token, outside the data directory. Read the token from a prompt instead of typing it inline, so it never lands in your shell history:

$ read -s -p 'Vault token: ' VAULT_TOKEN
$ kubectl create secret generic vault-token -n demo --from-literal=token="$VAULT_TOKEN"
secret/vault-token created
$ unset VAULT_TOKEN

Deploy the encrypted Postgres

apiVersion: kubedb.com/v1
kind: Postgres
metadata:
  name: tde-postgres
  namespace: demo
spec:
  version: "17.9-percona"
  replicas: 3
  storageType: Durable
  storage:
    accessModes:
    - ReadWriteOnce
    resources:
      requests:
        storage: 1Gi
  deletionPolicy: WipeOut
  tde:
    keyProvider:
      vault:
        address: https://vault.example.com:8200
        mountPath: secret
        tokenSecretRef:
          name: vault-token
    defaultEncryptedTables: true
    cipher: aes_128
$ kubectl apply -f https://github.com/kubedb/docs/raw/v2026.6.19/docs/guides/postgres/tde/guide/yamls/tde-postgres.yaml
postgres.kubedb.com/tde-postgres created

Wait for it to be Ready:

$ kubectl get pg -n demo tde-postgres
NAME           VERSION        STATUS   AGE
tde-postgres   17.9-percona   Ready    4m

defaultEncryptedTables: true makes tde_heap the default access method, so every table you create is encrypted without any extra syntax.

Verify encryption

Find the current primary pod (this cluster runs 3 replicas, so the primary may not be tde-postgres-0, especially after a failover), then exec into it to create a table and confirm it is encrypted:

$ kubectl get pods -n demo --selector="app.kubernetes.io/instance=tde-postgres,kubedb.com/role=primary" -o jsonpath='{.items[0].metadata.name}'
tde-postgres-0

$ kubectl exec -it -n demo tde-postgres-0 -c postgres -- bash

# inside the pod
$ psql
postgres=# CREATE TABLE secrets (id int, data text);
postgres=# INSERT INTO secrets VALUES (1, 'sensitive');
postgres=# SELECT pg_tde_is_encrypted('secrets');
 pg_tde_is_encrypted
---------------------
 t

pg_tde_is_encrypted returning t confirms the table’s data files are encrypted on disk. You can inspect the active principal key with SELECT pg_tde_key_info();.

To convert a pre-existing, unencrypted table:

ALTER TABLE legacy SET ACCESS METHOD tde_heap;

Rotate the principal key

The principal key wraps the per-relation internal keys, so rotating it does not rewrite any data and needs no restart. Use a RotatePrincipalKey PostgresOpsRequest:

apiVersion: ops.kubedb.com/v1alpha1
kind: PostgresOpsRequest
metadata:
  name: tde-rotate-key
  namespace: demo
spec:
  type: RotatePrincipalKey
  databaseRef:
    name: tde-postgres
  # optional: pin the new key name; otherwise one is generated
  rotatePrincipalKey:
    keyName: tde-postgres-principal-2
$ kubectl apply -f https://github.com/kubedb/docs/raw/v2026.6.19/docs/guides/postgres/tde/guide/yamls/rotate-principal-key.yaml
postgresopsrequest.ops.kubedb.com/tde-rotate-key created

$ kubectl get postgresopsrequest -n demo tde-rotate-key
NAME             TYPE                 STATUS       AGE
tde-rotate-key   RotatePrincipalKey   Successful   40s

Enable WAL encryption

WAL encryption is cluster-wide, requires a global provider (Vault or KMIP), and a rolling restart. Enable it with an EnableWALEncryption PostgresOpsRequest, which sets the server key, flips spec.tde.encryptWAL, and restarts every node:

apiVersion: ops.kubedb.com/v1alpha1
kind: PostgresOpsRequest
metadata:
  name: tde-enable-wal
  namespace: demo
spec:
  type: EnableWALEncryption
  databaseRef:
    name: tde-postgres
$ kubectl apply -f https://github.com/kubedb/docs/raw/v2026.6.19/docs/guides/postgres/tde/guide/yamls/enable-wal-encryption.yaml
postgresopsrequest.ops.kubedb.com/tde-enable-wal created

$ kubectl get postgresopsrequest -n demo tde-enable-wal
NAME             TYPE                  STATUS       AGE
tde-enable-wal   EnableWALEncryption   Successful   3m

$ kubectl exec -n demo tde-postgres-0 -c postgres -- psql -Atqc "SHOW pg_tde.wal_encrypt;"
on

WAL encryption is not compatible with every WAL tool. Validate your archiver (for example wal-g) against encrypted WAL before enabling it on a cluster with continuous archiving.

Cleanup

kubectl delete postgresopsrequest -n demo tde-rotate-key tde-enable-wal
kubectl delete pg -n demo tde-postgres
kubectl delete secret -n demo vault-token
kubectl delete ns demo

Next Steps