Automated PostgreSQL cluster deployment on K3s using Terraform with CloudNativePG operator and PgBouncer connection pooling.
- K3s Kubernetes cluster installed and running on target server
 - SSH access to the target server with sudo privileges
 - GitHub account with access to container registries (ghcr.io)
 
- PostgreSQL cluster using CloudNativePG operator
 - PgBouncer connection pooler
 - NodePort services for external database access
 - High availability with configurable replicas
 - Persistent storage
 
Copy and edit the configuration file:
cp terraform.tfvars.example terraform.tfvarsEdit terraform.tfvars:
github = {
  username = "your-github-username"
  token    = "ghp_your_github_personal_access_token"
}
server = {
  ssh_server           = "192.168.1.100"
  ssh_port             = 22
  ssh_username         = "ubuntu"
  ssh_password         = ""
  ssh_private_key_path = "~/.ssh/id_rsa"
}
postgres = {
  username = "postgres"
  password = "secure_password_here"
  replication_username = "replicator"
  replication_password = "replication_password"
}GitHub: Username and personal access token for container registry access
Server: SSH connection details (IP, port, username, key path)
PostgreSQL: Database credentials (username, password, replication credentials)
PgBouncer (optional): Pool mode (transaction/session/statement), max connections, pool size
Storage (optional): Size (default: 8Gi), storage class (default: local-path)
High Availability (optional): Enable HA (default: true), replica count (default: 2)
Data Preservation (optional): Preserve data on destroy (default: true), preserve PVCs (default: true)
See terraform.tfvars.example for all available options.
# Initialize Terraform
terraform init
# Preview changes
terraform plan
# Deploy PostgreSQL cluster
terraform apply
# View connection info
terraform output
# Destroy (respects preservation settings)
terraform destroyExternal access from DBMS tools (DBeaver, pgAdmin, etc.):
- PgBouncer (recommended): 
<server-ip>:30500 - PostgreSQL Direct: 
<server-ip>:30501 - PostgreSQL Alternative: 
<server-ip>:30502 
Using psql:
psql -h <server-ip> -p 30500 -U postgres -d postgresConnection string:
postgresql://postgres:your_password@<server-ip>:30500/postgresDBMS tools (DBeaver/pgAdmin/DataGrip):
- Host: Your server IP
 - Port: 30500
 - Database: postgres
 - Username: postgres
 - Password: Your configured password
 
# Via PgBouncer
pgbouncer-postgres.postgres.svc.cluster.local:5432
# Direct PostgreSQL
postgresql-postgres.postgres.svc.cluster.local:5432- System prerequisites (Git, curl, Docker)
 - CloudNativePG operator
 - PostgreSQL cluster in 
postgresnamespace - PgBouncer connection pooler
 - NodePort services for external access
 - GitHub container registry credentials
 
When enabled, deploys PostgreSQL with primary + replica instances (default: 3 total). Automatic failover managed by CloudNativePG operator.
By default, all data is preserved when running terraform destroy. To permanently delete data, set these to false in terraform.tfvars:
preserve_postgres_data_on_destroy = false
preserve_postgres_pvcs = falseInstall K3s first:
curl -sfL https://get.k3s.io | sh -Monitor cluster status:
kubectl get cluster postgresql-postgres -n postgres
kubectl get pods -n postgresCheck PgBouncer status:
kubectl get pods -n postgres -l app.kubernetes.io/name=pgbouncer
kubectl logs -n postgres -l app.kubernetes.io/name=pgbouncerCheck services and firewall:
kubectl get svc -n postgres
sudo ufw allow 30500/tcp- Change default passwords in production
 - Never commit 
terraform.tfvarsto version control - Use SSH key-based authentication
 - Configure firewall rules to restrict NodePort access
 - Use PgBouncer for connection pooling
 
- main.tf - Main Terraform configuration
 - variables.tf - Variable definitions
 - postgresql-cluster.yaml - PostgreSQL cluster template
 - pgbouncer-manifests.yaml - PgBouncer deployment
 - ingress.yaml - NodePort services
 - terraform.tfvars.example - Example configuration