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
PostgresVersionwithspec.distribution: Perconaandspec.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
- Review the TDE overview for the key hierarchy and limitations.
- Combine TDE (at rest) with TLS/SSL (in transit) for defense in depth.
- Set up backup, continuous archiving, and PITR for a TDE-encrypted Postgres.































