https://github.com/pascalinthecloud/terraform-module-cnpg-database
Terraform module for declarative PostgreSQL database creation in CloudNative-PG with automatic user and secret management.
https://github.com/pascalinthecloud/terraform-module-cnpg-database
Last synced: 4 months ago
JSON representation
Terraform module for declarative PostgreSQL database creation in CloudNative-PG with automatic user and secret management.
- Host: GitHub
- URL: https://github.com/pascalinthecloud/terraform-module-cnpg-database
- Owner: pascalinthecloud
- License: mit
- Created: 2026-01-13T20:23:23.000Z (5 months ago)
- Default Branch: main
- Last Pushed: 2026-02-05T20:58:46.000Z (4 months ago)
- Last Synced: 2026-02-06T01:04:29.157Z (4 months ago)
- Language: HCL
- Size: 57.6 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Terraform Module: CloudNative-PG Database
Terraform module for creating PostgreSQL databases in CloudNative-PG clusters with automatic user and secret management.
## Features
- 🔐 Automatic password secret creation with hot-reload support
- 📦 Database and user provisioned declaratively
- 🔌 Connection details secret for easy application integration
- 🏷️ Customizable labels for all resources
- ✅ Input validation for security best practices
- 💾 S3-based backup configuration with CloudNativePG/Barman
- 📅 Automated scheduled backups with retention policies
- 🔄 Point-in-Time Recovery (PITR) support
## Prerequisites
- CloudNative-PG operator installed in your Kubernetes cluster
## Usage
### Basic Example
```hcl
module "my_app_database" {
source = "github.com/pascalinthecloud/terraform-module-cnpg-database"
databases = [{
name = "my-app"
owner = "my_app_user"
password = var.database_password # Use a secure variable or secret manager
}]
cluster = {
name = "shared-postgres-prod"
namespace = "databases-prod"
}
}
```
### Complete Example with Labels
```hcl
module "my_app_database" {
source = "github.com/pascalinthecloud/terraform-module-cnpg-database"
databases = [{
name = "my-app"
owner = "my_app_user"
password = var.database_password
}]
cluster = {
name = "shared-postgres-prod"
namespace = "databases-prod"
}
labels = {
app = "my-app"
environment = "production"
managed-by = "terraform"
}
}
```
### Custom PostgreSQL Database Name
```hcl
module "my_app_database" {
source = "github.com/pascalinthecloud/terraform-module-cnpg-database"
databases = [{
name = "my-app"
pg_database_name = "myapp_production" # Custom PostgreSQL database name
owner = "my_app_user"
password = var.database_password
}]
cluster = {
name = "shared-postgres-prod"
namespace = "databases-prod"
}
}
```
### Connection Secret in Different Namespace
```hcl
module "my_app_database" {
source = "github.com/pascalinthecloud/terraform-module-cnpg-database"
databases = [{
name = "my-app"
owner = "my_app_user"
password = var.database_password
connection_secret_namespace = "my-app-namespace" # Deploy connection secret to app namespace
}]
cluster = {
name = "shared-postgres-prod"
namespace = "databases-prod"
}
}
```
### With S3 Backups Enabled
```hcl
module "my_app_database" {
source = "github.com/pascalinthecloud/terraform-module-cnpg-database"
databases = [{
name = "my-app"
owner = "my_app_user"
password = var.database_password
}]
cluster = {
name = "shared-postgres-prod"
namespace = "databases-prod"
}
# Enable S3 backups
backup = {
enabled = true
s3_endpoint_url = "https://s3.amazonaws.com" # Or your S3-compatible endpoint
s3_bucket_name = "my-postgres-backups"
s3_access_key_id = var.s3_access_key_id
s3_secret_access_key = var.s3_secret_access_key
retention_policy = "30d"
schedule = "0 2 * * *" # Daily at 2 AM UTC
target = "prefer-standby"
wal_compression = "gzip"
data_compression = "gzip"
}
labels = {
app = "my-app"
environment = "production"
}
}
```
## Requirements
| Name | Version |
|------|---------|
| [terraform](#requirement\_terraform) | >= 1.9.0 |
| [kubernetes](#requirement\_kubernetes) | >= 2.0 |
## Providers
| Name | Version |
|------|---------|
| [kubernetes](#provider\_kubernetes) | >= 2.0 |
## Modules
No modules.
## Resources
| Name | Type |
|------|------|
| [kubernetes_manifest.cluster](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/manifest) | resource |
| [kubernetes_manifest.database](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/manifest) | resource |
| [kubernetes_manifest.scheduled_backup](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/manifest) | resource |
| [kubernetes_role_binding_v1.backup_secret_reader](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/role_binding_v1) | resource |
| [kubernetes_role_v1.backup_secret_reader](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/role_v1) | resource |
| [kubernetes_secret_v1.backup_credentials](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/secret_v1) | resource |
| [kubernetes_secret_v1.connection](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/secret_v1) | resource |
| [kubernetes_secret_v1.database_password](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/secret_v1) | resource |
## Inputs
| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| [backup](#input\_backup) | Backup configuration for S3-based backups using Barman |
object({
enabled = optional(bool, false)
s3_endpoint_url = optional(string, "")
s3_bucket_name = optional(string, "")
s3_access_key_id = optional(string, "")
s3_secret_access_key = optional(string, "")
retention_policy = optional(string, "30d")
schedule = optional(string, "0 2 * * *")
wal_compression = optional(string, "gzip")
data_compression = optional(string, "gzip")
jobs = optional(number, 2)
target = optional(string, "prefer-standby")
create_scheduled_backup = optional(bool, true)
immediate = optional(bool, false)
}) | `{}` | no |
| [cluster](#input\_cluster) | CloudNative-PG cluster configuration object | object({
name = optional(string, "default-cluster")
namespace = optional(string, "default")
instances = optional(number, 1)
storage_class = optional(string, "longhorn") # Override with your cluster's available storage class
storage_size = optional(string, "10Gi")
inherited_labels = optional(map(string), {})
inherited_annotations = optional(map(string), {})
postgresql_max_connections = optional(string, "100")
postgresql_shared_buffers = optional(string, "256MB")
postgresql_effective_cache_size = optional(string, "1GB")
postgresql_maintenance_work_mem = optional(string, "64MB")
postgresql_checkpoint_completion_target = optional(string, "0.9")
postgresql_wal_buffers = optional(string, "16MB")
postgresql_default_statistics_target = optional(string, "100")
postgresql_random_page_cost = optional(string, "1.1")
postgresql_effective_io_concurrency = optional(string, "200")
postgresql_work_mem = optional(string, "2621kB")
postgresql_min_wal_size = optional(string, "512MB")
postgresql_max_wal_size = optional(string, "2GB")
bootstrap_database = optional(string, "postgres")
bootstrap_owner = optional(string, "postgres")
enable_pod_monitor = optional(bool, true)
resources = optional(object({
requests = optional(object({
memory = optional(string, "512Mi")
cpu = optional(string, "250m")
}), {})
limits = optional(object({
memory = optional(string)
cpu = optional(string)
}), null)
}), {})
}) | `{}` | no |
| [databases](#input\_databases) | List of databases to create. Each object must have name, owner, password, and database\_reclaim\_policy.
If the list is empty, the cluster will be created with no managed database users.
Users can manually add roles to the cluster or add databases through this module later. | list(object({
name = string
owner = string
password = string
database_reclaim_policy = optional(string, "retain")
pg_database_name = optional(string, "")
create_connection_secret = optional(bool, true)
connection_secret_namespace = optional(string, "")
})) | `[]` | no |
| [labels](#input\_labels) | Additional labels to add to all resources | `map(string)` | `{}` | no |
## Outputs
| Name | Description |
|------|-------------|
| [backup\_destination\_path](#output\_backup\_destination\_path) | S3 destination path for backups |
| [backup\_enabled](#output\_backup\_enabled) | Whether backups are configured for this cluster |
| [backup\_secret\_name](#output\_backup\_secret\_name) | Name of the Kubernetes secret containing backup credentials |
| [connection\_host](#output\_connection\_host) | Database connection hostname |
| [connection\_port](#output\_connection\_port) | Database connection port |
| [connection\_secret\_names](#output\_connection\_secret\_names) | Names of the Kubernetes secrets containing connection details |
| [connection\_uris](#output\_connection\_uris) | Full PostgreSQL connection URIs for each database |
| [database\_names](#output\_database\_names) | Names of the created databases in PostgreSQL |
| [owner\_usernames](#output\_owner\_usernames) | Usernames of the database owners |
| [password\_secret\_names](#output\_password\_secret\_names) | Names of the Kubernetes secrets containing the database passwords |
| [scheduled\_backup\_name](#output\_scheduled\_backup\_name) | Name of the ScheduledBackup resource |
## Backup Configuration
This module supports S3-based backups using CloudNativePG's Barman integration.
### Prerequisites for Backups
1. **S3-Compatible Storage**: AWS S3, MinIO, or other S3-compatible storage
2. **S3 Bucket**: Pre-created bucket for storing backups
3. **S3 Credentials**: Access key ID and secret access key
### Backup Features
- **Continuous WAL Archiving**: Write-Ahead Logs continuously archived to S3
- **Scheduled Backups**: Automated backups based on cron schedule
- **Retention Policies**: Automatic cleanup of old backups
- **Compression**: Configurable compression (gzip, bzip2, snappy)
- **PITR**: Point-in-Time Recovery support
### Backup Configuration
The `backup` object accepts the following properties:
| Property | Description | Default | Required |
|----------|-------------|---------|----------|
| `enabled` | Enable S3 backups | `false` | No |
| `s3_endpoint_url` | S3 endpoint URL (empty for AWS S3) | `""` | When enabled |
| `s3_bucket_name` | S3 bucket name | `""` | When enabled |
| `s3_access_key_id` | S3 access key ID | `""` | When enabled |
| `s3_secret_access_key` | S3 secret access key | `""` | When enabled |
| `retention_policy` | Retention policy (e.g., "30d") | `"30d"` | No |
| `schedule` | Cron schedule | `"0 2 * * *"` | No |
| `wal_compression` | WAL compression algorithm | `"gzip"` | No |
| `data_compression` | Data compression algorithm | `"gzip"` | No |
| `jobs` | Parallel backup jobs | `2` | No |
| `target` | Backup target instance | `"prefer-standby"` | No |
| `create_scheduled_backup` | Create ScheduledBackup resource | `true` | No |
| `immediate` | Take immediate backup on creation | `false` | No |
### What Gets Created for Backups
When `backup.enabled = true`, the module creates:
1. **Kubernetes Secret**: Stores S3 credentials securely
2. **RBAC Role**: Allows cluster service account to read backup credentials
3. **RBAC RoleBinding**: Binds the role to the cluster's service account
4. **Cluster Backup Config**: Configures Barman object store in cluster spec
5. **ScheduledBackup Resource**: Creates automated backup schedule (if `create_scheduled_backup = true`)
### Backup Schedule Examples
```hcl
# Daily at 2 AM UTC
backup = {
schedule = "0 2 * * *"
}
# Every 6 hours
backup = {
schedule = "0 */6 * * *"
}
# Weekly on Sunday at 3 AM
backup = {
schedule = "0 3 * * 0"
}
# Monthly on the 1st at 1 AM
backup = {
schedule = "0 1 1 * *"
}
```
### Monitoring Backups
Check backup status:
```bash
# List all backups
kubectl get backups -n
# Check scheduled backup
kubectl get scheduledbackup -n
# Describe backup details
kubectl describe backup -n
# View cluster backup status
kubectl describe cluster -n
```
### Point-in-Time Recovery (PITR)
To restore from backups, create a new cluster with recovery configuration. See the [CloudNativePG documentation](https://cloudnative-pg.io/documentation/current/recovery/) for details.
## Connection Details Secret
When `create_connection_secret = true` (default), the module creates a secret with the following keys:
- `host` - Database hostname
- `port` - Database port (5432)
- `database` - Database name
- `username` - Database username
- `password` - Database password
- `uri` - Full connection URI
Example usage in a pod:
```yaml
env:
- name: DATABASE_HOST
valueFrom:
secretKeyRef:
name: my-app-db-connection
key: host
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: my-app-db-connection
key: uri
```
## How It Works
1. **Password Secret**: Creates a `kubernetes.io/basic-auth` secret with the `cnpg.io/reload` label for hot password updates
2. **Database CRD**: Creates a CloudNative-PG Database resource that triggers database creation
3. **Connection Secret**: Optionally creates a connection details secret for application use
The CloudNative-PG operator reconciles the managed role in the cluster and creates the database with the specified owner.
## Security Best Practices
- Always use a secure method to provide passwords (e.g., Terraform variables with encryption, secret managers)
- Never commit passwords to version control
- Use strong passwords (minimum 8 characters enforced by validation)
- Consider using Kubernetes RBAC to restrict access to password secrets
## License
MIT
## Author
Pascal Toepke