The root Dockerfile consumes a prebuilt Linux release bundle; it does not compile the repository inside Docker. On a Linux host matching the cluster architecture, prepare the same build context used by the release workflow:
For cross-architecture or multi-architecture images, produce the matching kandev-linux-x64.tar.gz and/or kandev-linux-arm64.tar.gz bundles first. For each platform, extract the bundle's top-level kandev/ directory into ctx/bundle/, copy docker-entrypoint.sh into ctx/, and build that context for the matching platform. A --platform flag alone does not cross-compile the native binaries. See the Docker guide for the context layout.
Kandev publishes two flavors: the default vanilla image (smallest, npm-installable agent CLIs only) and a :universal image (~1.4 GB) that adds language toolchains (Go, Rust, build-essential), linters, and Playwright Chromium system libs - useful when your agents work on Go/Rust/Python projects or drive headless browsers.
image: ghcr.io/kdlbs/kandev:universal
See the repository image guide for the full comparison, inclusion policy, and recipes for deriving your own image when you need something neither flavor includes.
kubectl port-forward svc/kandev 38429:38429# Open http://localhost:38429
Ingress: Edit k8s/ingress.yaml to set your domain, then apply. The ingress routes all traffic to the backend on port 38429; the Go backend serves API, WebSocket, and SPA traffic on that port.
No extra configuration is needed. The frontend automatically uses window.location.origin to reach the API, which works with any domain, reverse proxy, or ingress setup.
The kandev image ships with git, gh (GitHub CLI), node, and npm, but does not bundle the coding-agent CLIs (claude-code, codex, auggie, etc.) — agent choice is per-user, and bundling all of them would bloat the image significantly.
Looking to add tools beyond agent CLIs - language toolchains, build tools, internal CLIs? See the repository image guide for the universal-image option and recipes for deriving your own image.
To install an agent inside the running pod, open Settings → Agents in the UI and click Install on the agent card under "Available to Install". The backend runs the agent's hard-coded install script (npm install -g <pkg>) and rescans on success.
The image sets NPM_CONFIG_PREFIX=/data/.npm-global so user-installed npm globals land on the PV and survive pod restarts and image upgrades. The same persistence applies if you kubectl exec and install manually:
The image sets HOME=/data/home for the kandev user, so every CLI that writes its auth state under $HOME lands on the PV and survives pod restarts and image upgrades. This includes:
gh CLI — ~/.config/gh/hosts.yml
Claude Code — ~/.claude/.credentials.json, ~/.claude.json
Codex — ~/.codex/auth.json, ~/.codex/config.toml
Auggie — ~/.augment/session.json
GitHub Copilot — ~/.copilot/...
OpenCode, Amp — ~/.config/<tool>/...
So a one-time kubectl exec -it deployment/kandev -- gh auth login (or claude login, codex login, etc.) is enough; you do not need to redo it after kubectl set image or a helm upgrade.
The GitHub PAT configured in Settings → Integrations → GitHub is stored as a secret in the SQLite DB (or your external Postgres) and has always persisted. The HOME=/data/home setup covers the separate gh auth login flow that the backend falls back to when no GITHUB_TOKEN secret is set.
Kandev reads configuration via KANDEV_-prefixed environment variables (Viper). Put only non-sensitive values in k8s/configmap.yaml; inject passwords and signing keys into the Deployment from a Kubernetes Secret.
See configuration.md for the full reference (every backend knob and its YAML form). The tables below cover what's most commonly set in K8s manifests.
Env Var
Required
Default
Description
KANDEV_SERVER_PORT
No
38429
Server port (API + WebSocket + Web UI)
KANDEV_HOME_DIR
No
/data
Kandev home directory - contains data/ (DB), tasks/, worktrees/, repos/, sessions/, and lsp-servers/
KANDEV_DATABASE_DRIVER
No
sqlite
Database driver (sqlite or postgres)
KANDEV_DATABASE_PATH
No
$KANDEV_HOME_DIR/data/kandev.db
SQLite database file path (override)
KANDEV_NATS_URL
No
empty
Shared NATS event bus URL. Required when multiple backend replicas must share events; empty selects a process-local in-memory bus.
KANDEV_DOCKER_ENABLED
No
false
Enable Docker runtime for agents (requires DinD)
KANDEV_LOG_LEVEL
No
info
Log level: debug, info, warn, error
KANDEV_LOGGING_FORMAT
No
environment-selected (json in K8s)
Explicit format: json or text. The literal value auto is not accepted.
KANDEV_LOGGING_OUTPUTPATH
No
stdout
Log destination: stdout, stderr, or a file path (rotated when a file)
KANDEV_LOGGING_MAXSIZEMB
No
100
Rotate the log file when it exceeds this size (MB). File output only.
KANDEV_LOGGING_MAXBACKUPS
No
5
Max rotated files to retain (0 = unlimited). File output only.
KANDEV_LOGGING_MAXAGEDAYS
No
30
Max age of rotated files in days (0 = unlimited). File output only.
KANDEV_LOGGING_COMPRESS
No
true
Gzip rotated files. File output only.
Logging in K8s: prefer the default stdout so kubelet collects logs. If you set KANDEV_LOGGING_OUTPUTPATH to a file, the active log is created with mode 0600 (owner read/write only); any sidecar reading it must run as the same user.
Upgrading from a pre-KANDEV_HOME_DIR deployment? The SQLite DB path moved from /data/kandev.db to /data/data/kandev.db, and KANDEV_DATA_DIR is gone — point KANDEV_HOME_DIR at the same volume mount (/data) instead. (KANDEV_WORKTREE_BASEPATH still works as an explicit override if you want to keep worktrees outside the home dir.) The backend auto-migrates the legacy kandev.db (plus any -wal/-shm files) on first boot — look for Migrated SQLite database from pre-KANDEV_HOME_DIR location in the pod logs. If you'd rather pin the old path, set KANDEV_DATABASE_PATH=/data/kandev.db in the ConfigMap.
Keep KANDEV_DATABASE_PASSWORD, KANDEV_AUTH_JWTSECRET, and KANDEV_OFFICE_JWTSIGNINGKEY out of ConfigMaps. Reference Secret keys from the Deployment instead:
Supply KANDEV_DATABASE_PASSWORD from a Secret as shown above. When using Postgres, the PVC is still needed for worktree storage but the database itself is external. An empty KANDEV_NATS_URL selects an isolated in-memory event bus in each process, so notifications and orchestration events do not coordinate across replicas; do not scale beyond one backend replica without shared NATS.
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 + NATS): Switch to Postgres, configure the same KANDEV_NATS_URL on every replica, change the deployment strategy to RollingUpdate, and increase replicas:
Rebuild the release bundle and refresh ctx/ using Building the Image before running the image build below.
# Build and push new imagedocker build -f Dockerfile -t your-registry.com/kandev:v1.1.0 ctxdocker push your-registry.com/kandev:v1.1.0# Update deploymentkubectl set image deployment/kandev kandev=your-registry.com/kandev:v1.1.0# Or edit the deployment directlykubectl edit deployment kandev
SQLite migrations run automatically on startup — no manual migration step needed.
Upgrading across the HOME=/data/home change: if you used kubectl exec to gh auth login or log in to agent CLIs on a pre-HOME=/data/home image, that state lived in the ephemeral /home/kandev and is not carried over. Log in once on the new pod and it will persist for all subsequent upgrades. If you want to keep the old state, copy it onto the PV before upgrading:
kubectl exec deployment/kandev -- sh -c 'cp -a /home/kandev/. /data/home/ 2>/dev/null || true'
# Check pod statuskubectl get pods -l app=kandev# View logskubectl logs -l app=kandev -f# Shell into the pod (if needed)kubectl exec -it deployment/kandev -- /bin/bash# Check PVC statuskubectl get pvc kandev-data# Describe pod for eventskubectl describe pod -l app=kandev