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:
|
||||
contents: write
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
build-web:
|
||||
@@ -114,6 +115,41 @@ jobs:
|
||||
dist/kandev-${{ matrix.platform }}.tar.gz
|
||||
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:
|
||||
needs: build-bundles
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -18,6 +18,8 @@ apps/
|
||||
- **Backend**: Go with Make (`make -C apps/backend test|lint|build`)
|
||||
- **Frontend**: Next.js (`cd apps && pnpm --filter @kandev/web dev|lint|typecheck`)
|
||||
- **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
|
||||
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 backendEnv = buildBackendEnv({
|
||||
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