Files
kandev/docs/k8s.md
T

6.5 KiB

Kubernetes Deployment Guide

This guide covers building the Kandev Docker image and deploying it to a Kubernetes cluster.

Prerequisites

  • Docker (for building the image)
  • A container registry (Docker Hub, GHCR, ECR, etc.)
  • A Kubernetes cluster with kubectl configured
  • A StorageClass that supports ReadWriteOnce PVCs (for SQLite persistence)

Building the Image

From the repository root:

# Build for your current architecture
docker build -t kandev:latest .

# Build for a specific architecture (e.g., for amd64 clusters)
docker build --platform linux/amd64 -t kandev:latest .

# Multi-arch build (requires buildx)
docker buildx build --platform linux/amd64,linux/arm64 -t kandev:latest .

Using the Pre-built Image

Kandev publishes images to GitHub Container Registry. Pull directly:

docker pull ghcr.io/kdlbs/kandev:latest

Or reference it in your K8s deployment:

image: ghcr.io/kdlbs/kandev:latest

Deploying to Kubernetes

Quick Start

# Apply all manifests
kubectl apply -f k8s/

# Check status
kubectl get pods -l app=kandev
kubectl logs -l app=kandev -f

What Gets Created

Resource File Purpose
Deployment deployment.yaml Single-replica pod running backend + web
Service service.yaml ClusterIP exposing ports 8080 (API) and 3000 (UI)
ConfigMap configmap.yaml Non-sensitive environment configuration
PVC pvc.yaml 10Gi persistent volume for SQLite + worktrees
Ingress ingress.yaml Example ingress with WebSocket support

Accessing the UI

Port-forward (quickest for testing):

kubectl port-forward svc/kandev 3000:3000 8080:8080
# Open http://localhost:3000

Ingress: Edit k8s/ingress.yaml to set your domain, then apply. The ingress routes /api and /ws to the backend (port 8080) and everything else to the web UI (port 3000).

Custom Domain / Reverse Proxy

When serving Kandev behind an ingress with a custom domain (e.g., https://kandev.example.com), set KANDEV_PUBLIC_URL so the frontend knows to use the ingress URL instead of trying to reach the backend on port 8080 directly:

# In k8s/configmap.yaml
KANDEV_PUBLIC_URL: "https://kandev.example.com"

Without this, the browser tries to connect to https://kandev.example.com:8080/api/... which doesn't exist — only the ingress port (443) is exposed externally.

Configuration

Kandev reads configuration via KANDEV_-prefixed environment variables (Viper). Set these in k8s/configmap.yaml or as environment variables in the deployment.

Core Settings

Env Var Default Description
KANDEV_PUBLIC_URL (unset) External URL when behind a reverse proxy (e.g., https://kandev.example.com)
KANDEV_SERVER_PORT 8080 Backend API port
KANDEV_DATA_DIR /data Base directory for all data (DB, worktrees, sessions, etc.)
KANDEV_DATABASE_DRIVER sqlite Database driver (sqlite or postgres)
KANDEV_DATABASE_PATH $KANDEV_DATA_DIR/kandev.db SQLite database file path (override)
KANDEV_DOCKER_ENABLED false Enable Docker runtime for agents (requires DinD)
KANDEV_LOG_LEVEL info Log level: debug, info, warn, error
KANDEV_LOGGING_FORMAT auto Log format: json (auto-detected in K8s) or text

PostgreSQL Settings (when KANDEV_DATABASE_DRIVER=postgres)

Env Var Default Description
KANDEV_DATABASE_HOST localhost PostgreSQL host
KANDEV_DATABASE_PORT 5432 PostgreSQL port
KANDEV_DATABASE_USER kandev Database user
KANDEV_DATABASE_PASSWORD (empty) Database password
KANDEV_DATABASE_DBNAME kandev Database name
KANDEV_DATABASE_SSLMODE disable SSL mode

Database: SQLite vs PostgreSQL

SQLite (default)

  • Zero-config, works out of the box
  • Database stored at /data/kandev.db on the PV
  • Single replica only (SQLite is single-writer)
  • Deployment strategy is Recreate to prevent concurrent writes
  • Good for small teams / personal use
  • Supports multiple replicas for horizontal scaling
  • Change deployment strategy to RollingUpdate
  • Set via environment variables:
# In configmap.yaml or a Secret
KANDEV_DATABASE_DRIVER: postgres
KANDEV_DATABASE_HOST: postgres.default.svc.cluster.local
KANDEV_DATABASE_PORT: "5432"
KANDEV_DATABASE_USER: kandev
KANDEV_DATABASE_PASSWORD: <from-secret>
KANDEV_DATABASE_DBNAME: kandev

When using Postgres, the PVC is still needed for worktree storage but the database itself is external.

Persistent Storage

The PVC at /data stores:

  • SQLite database (/data/kandev.db, /data/kandev.db-wal, /data/kandev.db-shm)
  • Git worktrees (/data/worktrees/) for workspace isolation

The PVC uses ReadWriteOnce access mode. If your cluster requires a specific StorageClass, add it to k8s/pvc.yaml:

spec:
  storageClassName: your-storage-class

Health Checks

The deployment includes both probes on the backend /health endpoint:

  • Liveness probe: Restarts the pod if the backend becomes unresponsive (30s interval, 3 failures)
  • Readiness probe: Removes the pod from service during startup or issues (10s interval, 3 failures)

The CLI launcher also performs an internal health check — it waits for the backend to be healthy before starting the web server.

Scaling

Single replica (SQLite): The default configuration uses replicas: 1 with Recreate strategy. This ensures only one instance writes to SQLite at a time.

Multiple replicas (PostgreSQL): Switch to Postgres, change the deployment strategy to RollingUpdate, and increase replicas:

spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0

Upgrading

# Build and push new image
docker build -t your-registry.com/kandev:v1.1.0 .
docker push your-registry.com/kandev:v1.1.0

# Update deployment
kubectl set image deployment/kandev kandev=your-registry.com/kandev:v1.1.0

# Or edit the deployment directly
kubectl edit deployment kandev

SQLite migrations run automatically on startup — no manual migration step needed.

Troubleshooting

# Check pod status
kubectl get pods -l app=kandev

# View logs
kubectl logs -l app=kandev -f

# Shell into the pod (if needed)
kubectl exec -it deployment/kandev -- /bin/bash

# Check PVC status
kubectl get pvc kandev-data

# Describe pod for events
kubectl describe pod -l app=kandev