OrbStack is part of my documented core stack, the same list as AWS and Docker, and the reason is specific: it finally made Kubernetes on a Mac cheap enough, in RAM and in patience, that I actually use it daily instead of admiring it from a distance. I had run Docker and Compose for years and kept deferring local Kubernetes because the tooling tax was absurd. Then I needed to ship InfraWhisper, a monitoring and analytics platform I built with nine services (dashboard, API server, AI engine, data collectors, Postgres, Redis, and supporting workers), and Compose stopped being an honest rehearsal for how it would run in production. This post is the exact path from zero to those nine services running on a local cluster.

Why OrbStack specifically
Three practical differences from the Docker Desktop and minikube era, all felt daily rather than read in a changelog:
- It is light. OrbStack idles quietly where Docker Desktop used to sit hot on my Mac. Running a full cluster plus my normal dev workload no longer means listening to fans.
- Kubernetes is a checkbox. You enable it in OrbStack's settings, it starts in seconds, and
kubectlgets a context namedorbstack. No VM management, no driver flags. - Your local images just work. This is the killer feature for iteration speed: images you build with
docker buildare usable by the cluster directly, no registry push, no image-loading dance. My inner loop is build, apply, test. When I later wanted GitOps-style deploys on the same cluster, Argo CD on this exact OrbStack setup slotted straight in.
Switching contexts is normal kubectl:
kubectl config use-context orbstack
kubectl get nodes
One node, your Mac. That single-node honesty matters later.
The six concepts you actually need
Kubernetes has a vocabulary problem, but for a working local deployment you need six things, and everything else can wait. A Pod runs your container. A Deployment keeps N replicas of a pod running and handles rollouts. A Service gives pods a stable DNS name inside the cluster. A ConfigMap and a Secret hold configuration and credentials. A Namespace keeps a project's resources grouped so you can delete the whole experiment in one command. InfraWhisper uses nothing more exotic than those, plus one PersistentVolumeClaim so Postgres survives restarts.
Deploying the platform, in the order that avoids pain
Everything lives in a deploy/k8s/ directory in the repo, applied in dependency order. Namespace first:
kubectl create namespace infrawhisper
kubectl config set-context --current --namespace=infrawhisper
Then the stateful stuff. Postgres gets a Deployment, a Service, and a PVC. The part beginners skip and regret is the volume; without it, every pod restart is a fresh empty database:
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres
spec:
replicas: 1
selector:
matchLabels: { app: postgres }
template:
metadata:
labels: { app: postgres }
spec:
containers:
- name: postgres
image: postgres:16
envFrom:
- secretRef: { name: infrawhisper-secrets }
volumeMounts:
- name: pgdata
mountPath: /var/lib/postgresql/data
volumes:
- name: pgdata
persistentVolumeClaim: { claimName: pgdata }
Config and secrets before the apps that consume them, because a Deployment referencing a missing Secret sits in CreateContainerConfigError and beginners read that as a broken image. Then Redis, then the API server, AI engine, and collectors, each a small Deployment plus Service, then the dashboard last. The app services all follow one template with different images and env, which is the moment Kubernetes starts paying rent: nine services is nine short files that look almost identical, not nine bespoke run scripts.
Apply the lot:
kubectl apply -f deploy/k8s/
kubectl get pods -w
Watching pods go ContainerCreating to Running the first time is genuinely satisfying. It will not all go green the first time, which brings us to the useful part.
The deploy, update, rollback loop
The reason to rehearse Kubernetes locally is this loop, because it is identical in production:
# ship a new image
docker build -t infrawhisper-api:v2 ./api
kubectl set image deployment/api api=infrawhisper-api:v2
# watch the rollout
kubectl rollout status deployment/api
# it's bad? one command back
kubectl rollout undo deployment/api
The first time a broken build rolled out and rollout undo had the old version serving again in seconds, the whole abstraction justified itself. Compose has no equivalent of that safety net, and that, more than scaling, is what I actually wanted from Kubernetes for a nine-service system.
What went wrong, and the commands that found it
A truthful log from getting InfraWhisper up, because the errors are where the learning is:
CrashLoopBackOffon the API server. The pod was dying before logs seemed to exist. The command that matters iskubectl logs api-<pod> --previous, which shows the crashed container's output, not the fresh one's. Mine was a database DSN pointing atlocalhostinstead of thepostgresservice name. Inside a cluster, localhost is the pod itself.ImagePullBackOffon a collector. The manifest saidimagePullPolicy: Always, which forces a registry pull even when the image exists locally. For locally built images, name a specific tag and let the policy default sensibly, or setIfNotPresent.- AI engine OOMKilled. I had copied production-ish memory limits onto a laptop.
kubectl describe podshows the OOMKilled reason plainly. Local limits should reflect the machine you have; a Mac running nine services is a real constraint, and setting requests low keeps scheduling honest. - Everything at once is undebuggable. When several pods are unhappy,
kubectl get events --sort-by=.lastTimestampgives the narrative in order. It is the first command I run now, not the last.
The honest parts
A local single-node cluster rehearses your manifests, your rollout discipline, and your service wiring. It does not rehearse real load balancers, multi-node scheduling, cloud IAM, or network policy enforcement, and pretending otherwise is how people get surprised in production. I treat OrbStack Kubernetes as the place where configuration mistakes get cheap, and my EC2-based production setups as the place where the remaining 20% lives. It is also not always the right tool: a single app with a queue worker is still better served by a supervisor-managed setup or plain Docker, the way I run n8n locally. Kubernetes earned its place here because nine interdependent services crossed the complexity line where its ceremony becomes cheaper than the alternative's chaos.
Need this wired up for your own stack?
This walkthrough is the compressed version of infrastructure work I do professionally, from local-cluster dev environments through AWS production deployments. If your team is staring at a Compose file that has outgrown itself, or you want a local Kubernetes rehearsal environment that matches your production topology, tell me what you are running and I will tell you honestly whether Kubernetes is even the right answer before we build anything.