Mini Everest — Kubernetes Operator for PostgreSQL/Valkey

September 23, 2026

Mini Everest project banner

1. One-line hook

A Kubernetes Operator in Go that automates the lifecycle of PostgreSQL/Valkey database clusters through a custom resource.

Status: in progress — actively building. CRD + reconciler + idempotent scaling working today.

Source: github.com/vaibhavmahiwal/mini_everest

2. The problem

Normally, deploying a production database on Kubernetes means hand-writing and maintaining YAML for StatefulSets, storage, networking, and backups. This project lets someone declare replicas: 3 and have the system provision and continuously maintain everything underneath automatically.

In other words: the user declares what they want (a DatabaseCluster), the operator handles how it stays that way.

3. Architecture

DatabaseCluster CR │ ▼ Reconciler (controller-runtime) │ ├── Deployment / StatefulSet ├── Service ├── PVC (in progress) └── CronJob for backups (planned)

Flow: CR → Reconciler watches → CreateOrUpdate child resources → update status → requeue on drift. A clean box diagram beats a wall of text — this is the 5-second version.

4. What's actually working right now

Currently implemented:

  • Custom Resource Definition (DatabaseCluster)
  • controller-runtime-based reconciler watching the CR
  • Automated Deployment provisioning and scaling from spec
  • Verified idempotent reconciliation

In progress:

  • StatefulSet + PVC-based storage
  • Multi-engine support (PostgreSQL / Valkey)
  • Automated backups via CronJob

5. One real technical decision

Why controllerutil.CreateOrUpdate instead of hand-rolled Get/Create/Update.

Hand-rolled logic splits into three branches (get → not-found → create → else-update), each with its own error handling and race windows. CreateOrUpdate collapses that into a single mutate function that is fetched, mutated, and patched atomically — fewer drift bugs, less code, and idempotency for free on every requeue.

Related call: I built the raw client-go version (Pod Label Syncer in learn/) before reaching for the framework, so the controller-runtime abstractions (informers, caches, workqueues) map to something concrete instead of magic. And spec stays user-writable while status is controller-owned — never letting users write status avoids conflicting sources of truth.

6. A real bug / challenge story

Early on, scaling replicas: 2 → 3 worked once, then fought itself on the next loop — the reconciler kept issuing updates even when nothing changed, causing hot-loop requeues.

Root cause: the mutate function was setting fields unconditionally (e.g. defaulting + pointer churn), so each pass looked "dirty" to the API server. Fix: make mutation declarative and compare-first — only set fields from spec, leave server-defaulted fields alone, and let CreateOrUpdate return unchanged when there is no diff. That one fix taught me the core operator lesson: a reconciler must converge to a no-op.

Use this story in interviews: problem → hot loop → unconditional mutation → declarative diff → idempotent no-op.

GitHub
LinkedIn