feat: add Dockerfile, K8s manifests, and deployment docs (#303)
This commit is contained in:
@@ -0,0 +1,51 @@
|
|||||||
|
# Git
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
|
||||||
|
# Build artifacts
|
||||||
|
apps/backend/bin/
|
||||||
|
apps/web/.next/
|
||||||
|
apps/web/out/
|
||||||
|
dist/
|
||||||
|
|
||||||
|
# Dependencies (rebuilt in Docker)
|
||||||
|
node_modules/
|
||||||
|
apps/web/node_modules/
|
||||||
|
apps/cli/node_modules/
|
||||||
|
apps/packages/*/node_modules/
|
||||||
|
|
||||||
|
# IDE and editor files
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# OS files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Docker
|
||||||
|
Dockerfile
|
||||||
|
.dockerignore
|
||||||
|
|
||||||
|
# CI/CD
|
||||||
|
.github/
|
||||||
|
|
||||||
|
# Documentation and config not needed at build time
|
||||||
|
docs/
|
||||||
|
k8s/
|
||||||
|
*.md
|
||||||
|
!apps/web/src/**/*.md
|
||||||
|
LICENSE
|
||||||
|
|
||||||
|
# Test artifacts
|
||||||
|
coverage/
|
||||||
|
*.test.ts
|
||||||
|
*.test.go
|
||||||
|
**/*_test.go
|
||||||
|
|
||||||
|
# Agent/Claude config
|
||||||
|
.claude/
|
||||||
|
.agents/
|
||||||
|
CLAUDE.md
|
||||||
|
AGENTS.md
|
||||||
@@ -8,6 +8,7 @@ on:
|
|||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
packages: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-web:
|
build-web:
|
||||||
@@ -114,6 +115,41 @@ jobs:
|
|||||||
dist/kandev-${{ matrix.platform }}.tar.gz
|
dist/kandev-${{ matrix.platform }}.tar.gz
|
||||||
dist/kandev-${{ matrix.platform }}.tar.gz.sha256
|
dist/kandev-${{ matrix.platform }}.tar.gz.sha256
|
||||||
|
|
||||||
|
docker:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Docker meta
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: ghcr.io/kdlbs/kandev
|
||||||
|
tags: |
|
||||||
|
type=semver,pattern={{version}}
|
||||||
|
type=semver,pattern={{major}}.{{minor}}
|
||||||
|
type=sha
|
||||||
|
|
||||||
|
- uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Login to GHCR
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Build and push
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
push: true
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
||||||
|
|
||||||
publish-release:
|
publish-release:
|
||||||
needs: build-bundles
|
needs: build-bundles
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ apps/
|
|||||||
- **Backend**: Go with Make (`make -C apps/backend test|lint|build`)
|
- **Backend**: Go with Make (`make -C apps/backend test|lint|build`)
|
||||||
- **Frontend**: Next.js (`cd apps && pnpm --filter @kandev/web dev|lint|typecheck`)
|
- **Frontend**: Next.js (`cd apps && pnpm --filter @kandev/web dev|lint|typecheck`)
|
||||||
- **UI**: Shadcn components via `@kandev/ui`
|
- **UI**: Shadcn components via `@kandev/ui`
|
||||||
|
- **GitHub repo**: `https://github.com/kdlbs/kandev`
|
||||||
|
- **Container image**: `ghcr.io/kdlbs/kandev` (GitHub Container Registry)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+111
@@ -0,0 +1,111 @@
|
|||||||
|
# Kandev Server Dockerfile
|
||||||
|
# Multi-stage build: Go backend + Next.js frontend + CLI launcher
|
||||||
|
#
|
||||||
|
# Build:
|
||||||
|
# docker build -t kandev:latest .
|
||||||
|
#
|
||||||
|
# Run:
|
||||||
|
# docker run -p 8080:8080 -p 3000:3000 -v kandev-data:/data kandev:latest
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Stage 1: Go builder — compile kandev + agentctl binaries
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
FROM golang:1.26-bookworm AS go-builder
|
||||||
|
|
||||||
|
WORKDIR /build
|
||||||
|
|
||||||
|
# Cache Go module downloads
|
||||||
|
COPY apps/backend/go.mod apps/backend/go.sum ./
|
||||||
|
RUN go mod download
|
||||||
|
|
||||||
|
# Copy backend source and build
|
||||||
|
COPY apps/backend/ ./
|
||||||
|
|
||||||
|
RUN go build -ldflags "-s -w" -o /out/kandev ./cmd/kandev && \
|
||||||
|
go build -ldflags "-s -w" -o /out/agentctl ./cmd/agentctl
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Stage 2: Web + CLI builder — build Next.js standalone + CLI
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
FROM node:24-slim AS web-builder
|
||||||
|
|
||||||
|
RUN corepack enable && corepack prepare pnpm@latest --activate
|
||||||
|
|
||||||
|
WORKDIR /build/apps
|
||||||
|
|
||||||
|
# Copy workspace config and all package.json files for dependency caching
|
||||||
|
COPY apps/package.json apps/pnpm-workspace.yaml apps/pnpm-lock.yaml ./
|
||||||
|
COPY apps/web/package.json ./web/package.json
|
||||||
|
COPY apps/cli/package.json ./cli/package.json
|
||||||
|
COPY apps/packages/ui/package.json ./packages/ui/package.json
|
||||||
|
COPY apps/packages/theme/package.json ./packages/theme/package.json
|
||||||
|
COPY apps/packages/types/package.json ./packages/types/package.json
|
||||||
|
|
||||||
|
RUN pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
# Copy full source for build
|
||||||
|
COPY apps/ ./
|
||||||
|
|
||||||
|
# Build shared packages, web app, and CLI
|
||||||
|
RUN pnpm --filter @kandev/web build && \
|
||||||
|
pnpm --filter kandev build
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Stage 3: Runtime — minimal image with both services
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
FROM node:24-bookworm-slim AS runtime
|
||||||
|
|
||||||
|
# Install only essential runtime dependencies, then clean up
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get install -y --no-install-recommends \
|
||||||
|
git \
|
||||||
|
ca-certificates \
|
||||||
|
tini && \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Create app directory structure matching what the CLI expects:
|
||||||
|
# /app/apps/backend/bin/kandev
|
||||||
|
# /app/apps/web/.next/standalone/web/server.js
|
||||||
|
RUN mkdir -p /app/apps/backend/bin /app/apps/web/.next /data/worktrees
|
||||||
|
|
||||||
|
# Copy Go binaries
|
||||||
|
COPY --from=go-builder /out/kandev /app/apps/backend/bin/kandev
|
||||||
|
COPY --from=go-builder /out/agentctl /usr/local/bin/agentctl
|
||||||
|
|
||||||
|
# Copy Next.js standalone output
|
||||||
|
COPY --from=web-builder /build/apps/web/.next/standalone/ /app/apps/web/.next/standalone/
|
||||||
|
|
||||||
|
# Copy static assets and public directory into standalone output
|
||||||
|
COPY --from=web-builder /build/apps/web/.next/static/ /app/apps/web/.next/standalone/web/.next/static/
|
||||||
|
COPY --from=web-builder /build/apps/web/public/ /app/apps/web/.next/standalone/web/public/
|
||||||
|
|
||||||
|
# Install CLI: copy built output + package.json, install prod deps, link binary
|
||||||
|
COPY --from=web-builder /build/apps/cli/dist/ /usr/local/lib/kandev-cli/dist/
|
||||||
|
COPY --from=web-builder /build/apps/cli/bin/ /usr/local/lib/kandev-cli/bin/
|
||||||
|
COPY --from=web-builder /build/apps/cli/package.json /usr/local/lib/kandev-cli/
|
||||||
|
RUN cd /usr/local/lib/kandev-cli && npm install --omit=dev && \
|
||||||
|
chmod +x bin/cli.js && \
|
||||||
|
ln -s /usr/local/lib/kandev-cli/bin/cli.js /usr/local/bin/kandev
|
||||||
|
|
||||||
|
# Data directory for SQLite and worktrees
|
||||||
|
VOLUME ["/data"]
|
||||||
|
|
||||||
|
# Environment defaults for containerized operation
|
||||||
|
ENV KANDEV_NO_BROWSER=1 \
|
||||||
|
KANDEV_DATABASE_PATH=/data/kandev.db \
|
||||||
|
KANDEV_WORKTREE_BASEPATH=/data/worktrees \
|
||||||
|
KANDEV_DOCKER_ENABLED=false \
|
||||||
|
HOSTNAME=0.0.0.0 \
|
||||||
|
NODE_ENV=production
|
||||||
|
|
||||||
|
# Run as non-root
|
||||||
|
RUN chown -R node:node /app /data
|
||||||
|
USER node
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
EXPOSE 8080 3000
|
||||||
|
|
||||||
|
# tini as PID 1 for signal handling; CLI manages backend + web processes
|
||||||
|
ENTRYPOINT ["tini", "--"]
|
||||||
|
CMD ["kandev", "start", "--backend-port", "8080", "--web-port", "3000", "--verbose"]
|
||||||
@@ -110,7 +110,7 @@ export async function runStart({
|
|||||||
|
|
||||||
// Production mode: use warn log level for clean output unless verbose/debug
|
// Production mode: use warn log level for clean output unless verbose/debug
|
||||||
const showOutput = verbose || debug;
|
const showOutput = verbose || debug;
|
||||||
const dbPath = path.join(DATA_DIR, "kandev.db");
|
const dbPath = process.env.KANDEV_DATABASE_PATH || path.join(DATA_DIR, "kandev.db");
|
||||||
const logLevel = debug ? "debug" : verbose ? "info" : "warn";
|
const logLevel = debug ? "debug" : verbose ? "info" : "warn";
|
||||||
const backendEnv = buildBackendEnv({
|
const backendEnv = buildBackendEnv({
|
||||||
ports,
|
ports,
|
||||||
|
|||||||
+217
@@ -0,0 +1,217 @@
|
|||||||
|
# Docker Guide
|
||||||
|
|
||||||
|
Run Kandev in a Docker container. For Kubernetes deployment, see [k8s.md](k8s.md).
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run -p 8080:8080 -p 3000:3000 -v kandev-data:/data ghcr.io/kdlbs/kandev:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
Open `http://localhost:3000` in your browser.
|
||||||
|
|
||||||
|
## Using the Pre-built Image
|
||||||
|
|
||||||
|
Kandev publishes images to GitHub Container Registry for `linux/amd64` and `linux/arm64`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Latest release
|
||||||
|
docker pull ghcr.io/kdlbs/kandev:latest
|
||||||
|
|
||||||
|
# Specific version
|
||||||
|
docker pull ghcr.io/kdlbs/kandev:0.9.0
|
||||||
|
```
|
||||||
|
|
||||||
|
## Building from Source
|
||||||
|
|
||||||
|
From the repository root:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build for your current architecture
|
||||||
|
docker build -t kandev:latest .
|
||||||
|
|
||||||
|
# Build for a specific architecture
|
||||||
|
docker build --platform linux/amd64 -t kandev:latest .
|
||||||
|
```
|
||||||
|
|
||||||
|
## Data Persistence
|
||||||
|
|
||||||
|
Kandev stores its SQLite database and git worktrees in `/data`. Mount a volume to persist data across container restarts:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Named volume (recommended)
|
||||||
|
docker run -v kandev-data:/data ghcr.io/kdlbs/kandev:latest
|
||||||
|
|
||||||
|
# Bind mount to a host directory
|
||||||
|
docker run -v /path/on/host:/data ghcr.io/kdlbs/kandev:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
Without a volume, data is lost when the container is removed.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Configuration is done via `KANDEV_`-prefixed environment variables:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run -p 8080:8080 -p 3000:3000 \
|
||||||
|
-v kandev-data:/data \
|
||||||
|
-e KANDEV_LOG_LEVEL=debug \
|
||||||
|
ghcr.io/kdlbs/kandev:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
|----------|---------|-------------|
|
||||||
|
| `KANDEV_DATABASE_DRIVER` | `sqlite` | Database driver (`sqlite` or `postgres`) |
|
||||||
|
| `KANDEV_DATABASE_PATH` | `/data/kandev.db` | SQLite database file path |
|
||||||
|
| `KANDEV_LOG_LEVEL` | `info` | Log level: `debug`, `info`, `warn`, `error` |
|
||||||
|
| `KANDEV_LOGGING_FORMAT` | `text` | Log format: `text` or `json` |
|
||||||
|
| `KANDEV_DOCKER_ENABLED` | `false` | Enable Docker runtime for agents (see below) |
|
||||||
|
| `KANDEV_WORKTREE_BASEPATH` | `/data/worktrees` | Git worktree storage directory |
|
||||||
|
|
||||||
|
### PostgreSQL
|
||||||
|
|
||||||
|
To use PostgreSQL instead of SQLite:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run -p 8080:8080 -p 3000:3000 \
|
||||||
|
-e KANDEV_DATABASE_DRIVER=postgres \
|
||||||
|
-e KANDEV_DATABASE_HOST=host.docker.internal \
|
||||||
|
-e KANDEV_DATABASE_PORT=5432 \
|
||||||
|
-e KANDEV_DATABASE_USER=kandev \
|
||||||
|
-e KANDEV_DATABASE_PASSWORD=secret \
|
||||||
|
-e KANDEV_DATABASE_DBNAME=kandev \
|
||||||
|
ghcr.io/kdlbs/kandev:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ports
|
||||||
|
|
||||||
|
| Port | Service |
|
||||||
|
|------|---------|
|
||||||
|
| `8080` | Backend API + WebSocket |
|
||||||
|
| `3000` | Web UI |
|
||||||
|
|
||||||
|
Override ports by changing the published ports and passing CLI flags:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run -p 9080:9080 -p 4000:4000 \
|
||||||
|
-v kandev-data:/data \
|
||||||
|
ghcr.io/kdlbs/kandev:latest \
|
||||||
|
kandev start --backend-port 9080 --web-port 4000
|
||||||
|
```
|
||||||
|
|
||||||
|
## Docker Compose
|
||||||
|
|
||||||
|
Create a `docker-compose.yml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
kandev:
|
||||||
|
image: ghcr.io/kdlbs/kandev:latest
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
- "3000:3000"
|
||||||
|
volumes:
|
||||||
|
- kandev-data:/data
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
kandev-data:
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
### With PostgreSQL
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
kandev:
|
||||||
|
image: ghcr.io/kdlbs/kandev:latest
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
- "3000:3000"
|
||||||
|
volumes:
|
||||||
|
- kandev-data:/data
|
||||||
|
environment:
|
||||||
|
KANDEV_DATABASE_DRIVER: postgres
|
||||||
|
KANDEV_DATABASE_HOST: postgres
|
||||||
|
KANDEV_DATABASE_PORT: "5432"
|
||||||
|
KANDEV_DATABASE_USER: kandev
|
||||||
|
KANDEV_DATABASE_PASSWORD: secret
|
||||||
|
KANDEV_DATABASE_DBNAME: kandev
|
||||||
|
depends_on:
|
||||||
|
postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
postgres:
|
||||||
|
image: postgres:17
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: kandev
|
||||||
|
POSTGRES_PASSWORD: secret
|
||||||
|
POSTGRES_DB: kandev
|
||||||
|
volumes:
|
||||||
|
- postgres-data:/var/lib/postgresql/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U kandev"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 3s
|
||||||
|
retries: 5
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
kandev-data:
|
||||||
|
postgres-data:
|
||||||
|
```
|
||||||
|
|
||||||
|
## Docker-in-Docker (Agent Containers)
|
||||||
|
|
||||||
|
By default, `KANDEV_DOCKER_ENABLED=false` inside the container. To enable Docker-based agent execution, mount the Docker socket:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run -p 8080:8080 -p 3000:3000 \
|
||||||
|
-v kandev-data:/data \
|
||||||
|
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||||
|
-e KANDEV_DOCKER_ENABLED=true \
|
||||||
|
ghcr.io/kdlbs/kandev:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note:** Mounting the Docker socket gives the container full access to the host's Docker daemon. Only do this in trusted environments.
|
||||||
|
|
||||||
|
## Health Check
|
||||||
|
|
||||||
|
The backend exposes a `/health` endpoint:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl http://localhost:8080/health
|
||||||
|
```
|
||||||
|
|
||||||
|
For Docker health checks in compose:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 3
|
||||||
|
start_period: 15s
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# View logs
|
||||||
|
docker logs kandev
|
||||||
|
|
||||||
|
# Follow logs
|
||||||
|
docker logs -f kandev
|
||||||
|
|
||||||
|
# Shell into the container
|
||||||
|
docker exec -it kandev /bin/bash
|
||||||
|
|
||||||
|
# Check data volume
|
||||||
|
docker volume inspect kandev-data
|
||||||
|
```
|
||||||
+202
@@ -0,0 +1,202 @@
|
|||||||
|
# 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:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 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:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker pull ghcr.io/kdlbs/kandev:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
Or reference it in your K8s deployment:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
image: ghcr.io/kdlbs/kandev:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
## Deploying to Kubernetes
|
||||||
|
|
||||||
|
### Quick Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 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):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
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).
|
||||||
|
|
||||||
|
## 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_SERVER_PORT` | `8080` | Backend API port |
|
||||||
|
| `KANDEV_DATABASE_DRIVER` | `sqlite` | Database driver (`sqlite` or `postgres`) |
|
||||||
|
| `KANDEV_DATABASE_PATH` | `/data/kandev.db` | SQLite database file path |
|
||||||
|
| `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` |
|
||||||
|
| `KANDEV_WORKTREE_BASEPATH` | `/data/worktrees` | Git worktree storage directory |
|
||||||
|
|
||||||
|
### 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
|
||||||
|
|
||||||
|
### PostgreSQL (recommended for production)
|
||||||
|
|
||||||
|
- Supports multiple replicas for horizontal scaling
|
||||||
|
- Change deployment strategy to `RollingUpdate`
|
||||||
|
- Set via environment variables:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# 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`:
|
||||||
|
|
||||||
|
```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:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
spec:
|
||||||
|
replicas: 3
|
||||||
|
strategy:
|
||||||
|
type: RollingUpdate
|
||||||
|
rollingUpdate:
|
||||||
|
maxSurge: 1
|
||||||
|
maxUnavailable: 0
|
||||||
|
```
|
||||||
|
|
||||||
|
## Upgrading
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 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
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 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
|
||||||
|
```
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: kandev-config
|
||||||
|
labels:
|
||||||
|
app: kandev
|
||||||
|
data:
|
||||||
|
# Database — SQLite on the PV. Set KANDEV_DATABASE_DRIVER=postgres for production.
|
||||||
|
KANDEV_DATABASE_PATH: /data/kandev.db
|
||||||
|
|
||||||
|
# Disable Docker runtime (no Docker-in-Docker in K8s)
|
||||||
|
KANDEV_DOCKER_ENABLED: "false"
|
||||||
|
|
||||||
|
# Worktree storage on the PV
|
||||||
|
KANDEV_WORKTREE_BASEPATH: /data/worktrees
|
||||||
|
|
||||||
|
# Logging — auto-detects K8s and uses JSON format
|
||||||
|
KANDEV_LOG_LEVEL: info
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: kandev
|
||||||
|
labels:
|
||||||
|
app: kandev
|
||||||
|
spec:
|
||||||
|
# SQLite is single-writer — use 1 replica. Switch to Postgres for horizontal scaling.
|
||||||
|
replicas: 1
|
||||||
|
strategy:
|
||||||
|
type: Recreate
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: kandev
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: kandev
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: kandev
|
||||||
|
image: kandev:latest
|
||||||
|
ports:
|
||||||
|
- name: backend
|
||||||
|
containerPort: 8080
|
||||||
|
protocol: TCP
|
||||||
|
- name: web
|
||||||
|
containerPort: 3000
|
||||||
|
protocol: TCP
|
||||||
|
envFrom:
|
||||||
|
- configMapRef:
|
||||||
|
name: kandev-config
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 250m
|
||||||
|
memory: 512Mi
|
||||||
|
limits:
|
||||||
|
cpu: "2"
|
||||||
|
memory: 2Gi
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /health
|
||||||
|
port: backend
|
||||||
|
initialDelaySeconds: 15
|
||||||
|
periodSeconds: 30
|
||||||
|
timeoutSeconds: 5
|
||||||
|
failureThreshold: 3
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /health
|
||||||
|
port: backend
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 10
|
||||||
|
timeoutSeconds: 3
|
||||||
|
failureThreshold: 3
|
||||||
|
volumeMounts:
|
||||||
|
- name: data
|
||||||
|
mountPath: /data
|
||||||
|
volumes:
|
||||||
|
- name: data
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: kandev-data
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: kandev
|
||||||
|
labels:
|
||||||
|
app: kandev
|
||||||
|
annotations:
|
||||||
|
# Adjust annotations for your ingress controller (nginx, traefik, etc.)
|
||||||
|
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
|
||||||
|
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
|
||||||
|
# WebSocket support
|
||||||
|
nginx.ingress.kubernetes.io/proxy-http-version: "1.1"
|
||||||
|
nginx.ingress.kubernetes.io/configuration-snippet: |
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
spec:
|
||||||
|
rules:
|
||||||
|
- host: kandev.example.com
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
# Backend API + WebSocket
|
||||||
|
- path: /api
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: kandev
|
||||||
|
port:
|
||||||
|
name: backend
|
||||||
|
- path: /ws
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: kandev
|
||||||
|
port:
|
||||||
|
name: backend
|
||||||
|
- path: /health
|
||||||
|
pathType: Exact
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: kandev
|
||||||
|
port:
|
||||||
|
name: backend
|
||||||
|
# Web UI (catch-all)
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: kandev
|
||||||
|
port:
|
||||||
|
name: web
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: kandev-data
|
||||||
|
labels:
|
||||||
|
app: kandev
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 10Gi
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: kandev
|
||||||
|
labels:
|
||||||
|
app: kandev
|
||||||
|
spec:
|
||||||
|
type: ClusterIP
|
||||||
|
selector:
|
||||||
|
app: kandev
|
||||||
|
ports:
|
||||||
|
- name: backend
|
||||||
|
port: 8080
|
||||||
|
targetPort: backend
|
||||||
|
protocol: TCP
|
||||||
|
- name: web
|
||||||
|
port: 3000
|
||||||
|
targetPort: web
|
||||||
|
protocol: TCP
|
||||||
Reference in New Issue
Block a user