Local K8s with kind
The platform includes a Makefile-driven workflow for running the full stack locally in a kind (Kubernetes in Docker) cluster.
Prerequisites
| Tool | Check Command |
|---|---|
| Docker | docker --version |
| kind | kind --version |
| kubectl | kubectl version --client |
| Bun | bun --version |
| Doppler CLI | doppler --version |
Quick Start
make dev-k8s # Bootstrap: kind cluster + nginx ingress + build + deploy
make dev-k8s-status # Check pod status
curl http://shift.lvh.me/healthz
Make Targets
| Command | Description |
|---|---|
make dev-k8s | Full bootstrap: creates kind cluster, installs NGINX ingress controller, builds Docker image, loads it into kind, applies dev overlay, waits for pods |
make dev-k8s-status | Show pod status in the shift-platform namespace |
make dev-k8s-logs | Tail gateway logs |
make dev-k8s-build | Rebuild the Docker image and reload it into the kind cluster |
make dev-k8s-deploy | Apply Kustomize dev overlay and trigger a rollout restart |
make dev-k8s-down | Delete the kind cluster entirely |
make seed-k8s | Seed the platform with sample data (requires Doppler) |
make golden-path-k8s | Run the golden path E2E test against the local cluster |
Cluster Topology
kind cluster (shift-platform)
|-- Namespace: shift-platform
| |-- Deployment: shift-gateway (1 replica, shift-platform:dev)
| | +-- ConfigMap: shift-platform-config
| | |-- SHIFT_STORAGE=convex
| | +-- CONVEX_SELF_HOSTED_URL=http://convex-backend.shift-platform.svc.cluster.local:3210
| |-- Deployment: convex-backend (1 replica, ghcr.io/get-convex/convex-backend)
| |-- Service: shift-gateway (ClusterIP 80 -> 3000)
| |-- Service: convex-backend (ClusterIP 3210)
| +-- Ingress: shift-gateway (host: shift.lvh.me, nginx)
+-- Namespace: ingress-nginx
+-- NGINX Ingress Controller
Injecting Secrets
After the cluster is up, inject secrets from Doppler:
doppler run --project platform --config dev -- sh -c '\
kubectl create secret generic shift-platform-secrets \
-n shift-platform \
--from-literal=GOOGLE_CLIENT_ID="$GOOGLE_CLIENT_ID" \
--from-literal=GOOGLE_CLIENT_SECRET="$GOOGLE_CLIENT_SECRET" \
--from-literal=GOOGLE_CLI_CLIENT_ID="$GOOGLE_CLI_CLIENT_ID" \
--from-literal=GOOGLE_CLI_CLIENT_SECRET="$GOOGLE_CLI_CLIENT_SECRET" \
--from-literal=SESSION_SECRET="$SESSION_SECRET" \
--from-literal=SHIFT_API_KEY="$SHIFT_API_KEY" \
--dry-run=client -o yaml | kubectl apply -f -'
Then restart the gateway to pick up the secrets:
kubectl rollout restart deployment/shift-gateway -n shift-platform
kubectl rollout status deployment/shift-gateway -n shift-platform --timeout=60s
Day-to-Day Workflow
After the initial bootstrap, the typical rebuild cycle is:
# Make code changes, then:
make dev-k8s-build # Rebuild Docker image + reload into kind
make dev-k8s-deploy # Apply manifests + rollout restart
# Verify
make dev-k8s-status
curl http://shift.lvh.me/healthz
Running Tests Against Local K8s
# Seed sample data
make seed-k8s
# Run the full golden path E2E test
make golden-path-k8s
# Or run any test with the K8s gateway URL
GATEWAY_URL=http://shift.lvh.me bun test release/v0.1.0/golden-path/golden-path.test.ts
Teardown
make dev-k8s-down # Deletes the kind cluster
The kind cluster is stateless -- make dev-k8s recreates everything from scratch.