Terraform State Database¶
Overview¶
The Terraform State Database provides a PostgreSQL-based backend for storing Terraform state files using CloudNativePG. This eliminates the need for external state storage services and integrates seamlessly with the Skatzi platform's existing infrastructure.
Why PostgreSQL for Terraform State?¶
- State Locking: Native support prevents concurrent modifications
- Encryption at Rest: Data encrypted on Hetzner volumes
- ACID Compliance: Reliable transactions ensure state consistency
- Self-Hosted: No dependency on external services
- OpenBao Integration: Credentials managed centrally
Key Features¶
- CloudNativePG Operator: Automated PostgreSQL cluster management
- Persistent Storage: Dedicated 10GB Hetzner volume
- ExternalSecret Integration: Credentials synced from OpenBao
- High Availability Ready: Can be scaled to multiple instances
- Flux GitOps: Declarative deployment with dependency management
Architecture¶
┌─────────────────────────────────────────────────────────────┐
│ Gitea Action Runner │
│ (Terraform Execution) │
└────────────────────────┬────────────────────────────────────┘
│ postgres://
│ Connection String
▼
┌─────────────────────────────────────────────────────────────┐
│ PostgreSQL Cluster (CloudNativePG) │
│ terraform-state-db-rw.svc │
├─────────────────────────────────────────────────────────────┤
│ │
│ Database: terraform_state │
│ User: terraform_backend │
│ Table: states │
│ - id (SERIAL PRIMARY KEY) │
│ - name (TEXT UNIQUE) │
│ - data (TEXT) │
│ │
└────────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Hetzner Cloud Volume (10GB) │
│ Volume ID: 104482472 │
│ Persistent Storage (Retain) │
└─────────────────────────────────────────────────────────────┘
Credentials Flow
┌──────────┐ ┌─────────────┐ ┌──────────────┐
│ OpenBao │─────▶│ External │─────▶│ Kubernetes │
│ │ │ Secrets │ │ Secret │
│ secret/ │ │ Operator │ │ │
│ hetzner- │ │ │ │ terraform- │
│ mgmt/ │ │ │ │ state-db- │
│ terraform│ │ │ │ credentials │
│ -state-db│ │ │ │ │
└──────────┘ └─────────────┘ └──────────────┘
Components¶
1. CloudNativePG Operator¶
- Namespace:
cnpg-system - Purpose: Manages PostgreSQL cluster lifecycle
- Helm Chart:
cloudnative-pg/cloudnative-pgv0.27.0
2. PostgreSQL Cluster¶
- Namespace:
terraform-state - Cluster Name:
terraform-state-db - Instances: 1 (scalable to 3 for HA)
- Storage: 10Gi on Hetzner volume
- Database:
terraform_state - Owner:
terraform_backend
3. Persistent Storage¶
- PersistentVolume:
terraform-state-db-pv - Hetzner Volume ID:
104482472 - Size: 10GB
- Reclaim Policy: Retain (survives cluster deletion)
- Storage Class:
hcloud-volumes
4. Credentials Management¶
- ExternalSecret: Syncs from OpenBao
- OpenBao Path:
secret/hetzner-mgmt/terraform-state-db/credentials - Kubernetes Secret:
terraform-state-db-credentials - Fields:
username,password
Database Schema¶
The states table stores Terraform state files:
- id: Auto-incrementing primary key
- name: Unique identifier for the state (e.g., workspace name)
- data: JSON-serialized Terraform state
- Permissions: Full CRUD granted to
terraform_backenduser
Flux Kustomizations¶
The component is deployed via three Flux Kustomizations:
- cloudnative-pg: Deploys the operator
- terraform-state-db-secrets: Creates ExternalSecret
- terraform-state-db: Deploys PostgreSQL cluster
Dependency Chain¶
cloudnative-pg (operator)
↓
terraform-state-db-secrets (credentials)
↓
terraform-state-db (PostgreSQL cluster)
Connection Details¶
Internal (from Kubernetes)¶
Host: terraform-state-db-rw.terraform-state.svc.cluster.local
Port: 5432
Database: terraform_state
User: terraform_backend
Connection String Format¶
postgres://terraform_backend:PASSWORD@terraform-state-db-rw.terraform-state.svc.cluster.local:5432/terraform_state?sslmode=disable
SSL Mode
sslmode=disable is used within the cluster as traffic is encrypted by Cilium network policies.
Usage in Terraform¶
Backend Configuration¶
Gitea Actions Integration¶
steps:
- name: Terraform Init
run: terraform init
env:
TF_CLI_ARGS_init: "-backend-config=conn_str=${{ secrets.TF_STATE_CONN_STR }}"
Security¶
Authentication¶
- Database credentials stored in OpenBao
- Synced to Kubernetes via ExternalSecret
- Gitea Action Secrets for CI/CD pipelines
Network Security¶
- Traffic within cluster encrypted by Cilium
- No external access to database
- Accessible only from within Kubernetes cluster
Data Protection¶
- Encryption at rest on Hetzner volumes
- State locking prevents concurrent modifications
- Retain policy prevents accidental data loss
Monitoring¶
CloudNativePG provides built-in monitoring:
# Check cluster status
kubectl get cluster -n terraform-state
# View PostgreSQL logs
kubectl logs -n terraform-state terraform-state-db-1
# Monitor operator
kubectl logs -n cnpg-system deployment/cloudnative-pg
Quick Links¶
- Installation Guide - How the component was deployed
- Usage Guide - Managing Terraform state files
Resources¶
| Resource | URL/Command |
|---|---|
| Database Service | terraform-state-db-rw.terraform-state.svc.cluster.local:5432 |
| Cluster Status | kubectl get cluster -n terraform-state |
| Pods | kubectl get pods -n terraform-state |
| Credentials | kubectl get secret terraform-state-db-credentials -n terraform-state |
Troubleshooting¶
Connection Issues¶
# Test database connectivity from within cluster
kubectl run -it --rm debug --image=postgres:15 --restart=Never -- \
psql "postgres://terraform_backend:PASSWORD@terraform-state-db-rw.terraform-state.svc.cluster.local:5432/terraform_state"
View Stored States¶
kubectl exec -n terraform-state terraform-state-db-1 -- \
psql -U postgres -d terraform_state -c "SELECT name FROM states;"