
A practical CloudNativePG Terraform pattern for Kubernetes database management, cloud automation, and clearer operational ownership.
Provisioning a PostgreSQL cluster on Kubernetes can look deceptively complete: Terraform applies, the custom resource exists, and the pipeline turns green. The difficult work begins in the gap between “created” and “usable.”
Who owns credentials? How do dependent workloads know the cluster is ready? Which concerns belong to the database module, and which belong to the platform?
This article presents a bounded pattern for CloudNativePG Terraform deployments.
Terraform declares the infrastructure and its references, the secret-management workflow creates or retrieves the values. CloudNativePG consumes the resulting Kubernetes Secret reference.
A separate readiness boundary proves the runtime condition that the next dependency actually needs.
“An apply is an infrastructure event, not proof that an application can safely use the database.”
Infrastructure as code is most valuable when it makes a desired system easy to understand, review, and repeat. It becomes harder to operate when a single Terraform module absorbs responsibilities from every layer: database configuration, secret creation, secret-store access, application deployment, connection details, and readiness checks.
That design might work for one environment, but it creates an unclear operating model. A DevOps or platform engineer cannot readily see whether a failure belongs to cloud automation, the secret-management controller, the database operator, or the application release. The result is not just more code. There is more ambiguity during change, handover, and incident response.
For a Kubernetes database management pattern to scale, start by separating the responsibilities that must evolve independently:
This split does not make the components independent. It makes their contracts explicit.
Terraform can work with sensitive data, and a sensitive marker helps keep a value out of ordinary command output. That is useful, but it is not the same as ensuring that the value never reaches state or plan artifacts. HashiCorp documents that directly supplied secret values can be persisted in Terraform state and plan files, which enlarges the surface that must be protected.
The practical question is not “can Terraform ever receive a secret?” Some providers and workflows make this unavoidable. The better question is: does Terraform need the raw credential value to create the desired system, or does it only need a stable reference to the credential that a workload will consume?
For a CloudNativePG deployment, that distinction is often decisive. A cluster can reference an existing Kubernetes Secret by name during bootstrap. Terraform can therefore manage the cluster declaration without accepting a password as a module input or exposing it as an output.
“Make the secret reference part of the contract; keep the secret value in the system designed to manage it.”
This approach improves database security without presenting Terraform state as inherently unsafe. The state still needs remote storage, encryption, access controls, and audit discipline.
The goal is narrower: reduce unnecessary secret handling in an infrastructure workflow that is not acting as the organisation’s secrets management system.
A practical pattern begins with the Kubernetes Secret that CloudNativePG will consume, that Secret can come from an approved external secret store through an ExternalSecret resource, or it can be generated in the Kubernetes secret-management workflow when a value must be created rather than retrieved.
External Secrets Operator provides the ExternalSecret resource pattern: it fetches values through a configured store or generator and writes a target Kubernetes Secret. The exact store, authentication method, refresh policy, ownership policy, and rotation behaviour should be chosen and reviewed for the platform in question. They are not details to hide inside a generic database module.
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: payments-db-app
spec:
secretStoreRef:
name: platform-secrets
kind: ClusterSecretStore
target:
name: payments-db-app
template:
type: kubernetes.io/basic-auth
data:
- secretKey: username
remoteRef:
key: platform/payments/db
property: username
- secretKey: password
remoteRef:
key: platform/payments/db
property: password
The manifest declares intent, not credentials. The external store and the operator handle the value path; CloudNativePG and the application receive a Kubernetes Secret reference. Kubernetes itself recommends encryption at rest and least-privilege access to Secrets, because base64 encoding is not encryption and access to a Secret must be treated as sensitive.
This is also where teams should define rotation expectations. A database credential may be created once, periodically refreshed, or rotated through an explicit workflow. Do not assume that a Kubernetes Secret update automatically produces the desired database-credential lifecycle. Verify that behaviour for the chosen operator, CloudNativePG configuration, and application connection model.

The CloudNativePG cluster module should answer a limited set of questions:
It should not become the owner of external-secret-store setup, secret-value generation, application deployment, or application-level schema lifecycle. Those are connected concerns with different change cadence and different owners.
CloudNativePG supports an initdb bootstrap configuration that can reference a Kubernetes Secret containing initial application credentials. The referenced Secret must meet the documented basic-auth requirements, including a username that matches the configured application owner. The cluster manifest can remain focused on that reference:
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: payments-db
spec:
instances: 3
bootstrap:
initdb:
database: payments
owner: payments
secret:
name: payments-db-app
storage:
size: 20Gi
This is intentionally not a copy-paste production module. Storage, backups, observability, topology, resource limits, encryption, and recovery objectives need environment-specific design. The important architectural decision is that the cluster configuration receives a secret name, not a raw password.
That smaller interface has a database-management benefit: a reviewer can read the module inputs and understand what the cluster needs to exist. A security reviewer can focus on the secret path. An application team can focus on how it consumes the connection information. The boundaries make each discussion more concrete.
A Kubernetes resource existing is not equivalent to a PostgreSQL service being ready for a dependent workload. This matters especially in cloud automation, where a green pipeline can otherwise mask a race condition: the next resource starts before the operator has completed reconciliation, before the primary is ready, or before the application Secret is available.

CloudNativePG exposes conditions for its Cluster resource. Its documented Ready condition is true when the requested number of instances exists and the primary instance is ready. That makes it a reasonable baseline for a creation workflow, but it should not be treated as a universal definition of application readiness.
The right readiness boundary depends on the next dependency. For example:
“Define readiness from the next dependency backwards, rather than from the convenience of the provisioning tool.”
In other words, Terraform can create the resources and coordinate explicit dependencies, but runtime verification belongs in the deployment contract. Keep that contract observable. Record which condition was checked, which component owns it, and what happens when it is not met. This is where database activity monitoring and operational dashboards support the pattern: they do not replace readiness, but they help teams see the behaviour of the data service after rollout.
The pattern is easier to remember as four bounded layers:
The arrows matter. A CloudNativePG module may depend on the existence of the Secret resource, but it does not need the password value. An application may depend on a ready cluster, but it does not need to own the cluster’s storage or failover configuration.
This division also aligns with a clearer team model. Delivery and platform teams can own infrastructure-as-code execution and reusable modules, while SRE and operations teams can own runtime health signals, monitoring standards, and incident readiness. That separation should be reflected in runbooks and review gates, not merely in repository folders.
The main outcome is less operational ambiguity. Teams avoid reconstructing a one-off deployment design every time they add an environment or a PostgreSQL workload. A bounded module and its explicit references are easier to review, test, change, and hand over.
The pattern also supports a more credible approach to database security. Instead of claiming that a tool makes secrets safe, teams can describe exactly where sensitive values are created, stored, synchronized, mounted, and rotated. That is a stronger basis for a security review than a broad “best practice” label.
For engineering leaders, this becomes a database management system design decision rather than a narrow Terraform convention. The objective is not to centralize every responsibility in one module. It is to make the interfaces between infrastructure, secrets, databases, and applications reliable enough that the operating model survives the initial rollout.
Before the next environment rollout, ask:
CloudNativePG Terraform deployments become easier to operate when the cluster module stays scoped, secret values stay in an approved secret-management path, and readiness is treated as a runtime contract.
Terraform remains valuable for declaring the platform foundation. It should not be forced to become the control plane for every application and credential concern. By keeping secret references, PostgreSQL cluster provisioning, database activity monitoring, and application deployment as connected but separate responsibilities, teams create a clearer path from a successful apply to a usable service.
If your PostgreSQL deployment depends on Terraform, Kubernetes, and a secret store, review where credentials are created, referenced, persisted, and verified before the next rollout.