← Back to Blog

The Kaja Team

Why your database sits in Pending, and what a StorageClass is

A stateful workload that never starts is usually waiting for a disk nothing has offered. What is actually happening, and the one command to run before you add a database.

You add Postgres to a project. A minute later it still is not up, and the app bound to it cannot connect. Nothing looks broken: no crash loop, no error in the logs, no failed image pull. The pod simply never starts.

Almost always, that pod is not stuck. It is waiting. It asked the cluster for a disk, and nothing in the cluster has offered one.

A claim is an order form, not a disk

A database needs storage that outlives the pod, because pods get rescheduled and a database that loses its files on every restart is not a database. So the workload does not ask for a directory. It creates a PersistentVolumeClaim: a request that says "I need 20 GiB of read-write storage."

A claim is an order form. Something else has to fill it, and that something is usually dynamic provisioning: a driver that sees the claim, creates real storage, and binds it. The driver is selected by name through a StorageClassgp3 backed by EBS on AWS, local-path on a stock k3s install, whatever your storage vendor registered on-premises.

A claim that does not name a class gets the cluster's default StorageClass. If the cluster has no default, the claim stays Pending. Kubernetes does not raise this as an error, because an administrator might install a provisioner five minutes from now and the claim would then be satisfiable. So nothing fails. It waits, quietly, forever.

Ten seconds to check

List the classes and look for the one marked default:

kubectl get storageclass

If that prints nothing, or prints classes with no (default) beside any of them, that is your answer. To see what a specific claim is waiting on, read the events at the bottom of:

kubectl describe pvc <name> -n <namespace>

It will tell you plainly that no volumes are available and no class is set.

Which clusters have one

  • A stock k3s install ships local-path as its default, so single-node setups usually work out of the box. Worth knowing what it does: it carves the volume out of one node's own disk, which pins that workload to that node and gives you no replication. Fine for development, a decision to make consciously for anything else.
  • Managed clusters generally ship a default class, but the CSI driver that backs it can be a separate add-on. A cluster with a default class and no driver installed fails exactly the same way, only later and more confusingly.
  • Bare-metal and kubeadm clusters have nothing until you install something. This is the common case for a machine in your own building, and it is why the symptom shows up most often there.

Where this touches Kaja

Kaja does not bring its own storage layer. Every managed service that keeps data asks the cluster for a volume the same way any other workload would: Postgres, MySQL, Redis in its durable mode, and the log and metric stores. So a default StorageClass is a genuine precondition for the stateful half of the product, and no amount of console polish substitutes for it.

Stateless container apps are unaffected. You can connect a cluster, deploy from a repo, attach a domain and serve traffic without ever provisioning a volume. It is the first database that needs this.

The practical version: run kubectl get storageclass before you add your first stateful service. If you set the machine up with our k3s install script, you already have one. If you are pointing Kaja at an existing cluster somebody else built, it is a thirty-second check that saves an hour of wondering why a pod is doing nothing at all.