Skip to main content

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

ToolCheck Command
Dockerdocker --version
kindkind --version
kubectlkubectl version --client
Bunbun --version
Doppler CLIdoppler --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

CommandDescription
make dev-k8sFull bootstrap: creates kind cluster, installs NGINX ingress controller, builds Docker image, loads it into kind, applies dev overlay, waits for pods
make dev-k8s-statusShow pod status in the shift-platform namespace
make dev-k8s-logsTail gateway logs
make dev-k8s-buildRebuild the Docker image and reload it into the kind cluster
make dev-k8s-deployApply Kustomize dev overlay and trigger a rollout restart
make dev-k8s-downDelete the kind cluster entirely
make seed-k8sSeed the platform with sample data (requires Doppler)
make golden-path-k8sRun 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.