9.7 KiB
Docker Guide
Run Kandev in a Docker container. For Kubernetes deployment, see k8s.md.
Quick Start
docker run -p 38429:38429 -v kandev-data:/data ghcr.io/kdlbs/kandev:latest
Open http://localhost:38429 in your browser.
Using the Pre-built Image
Kandev publishes images to GitHub Container Registry for linux/amd64 and linux/arm64:
# Latest release
docker pull ghcr.io/kdlbs/kandev:latest
# Specific version
docker pull ghcr.io/kdlbs/kandev:0.9.0
Choosing your image: vanilla vs. universal
Two flavors are published. The default vanilla image is smallest and bundles npm-installable agent CLIs only. The :universal image (~1.4 GB) adds language toolchains (Go, Rust, build-essential), linters, and Playwright Chromium system libs - pick this if your agents work on Go/Rust/Python projects or drive headless browsers.
docker pull ghcr.io/kdlbs/kandev:universal
See images.md for the full comparison, inclusion policy, and recipes for deriving your own image.
Building from Source
From the repository root:
# 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:
# 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.
What lives on the volume
| Path | Contents |
|---|---|
/data/data/ |
SQLite database (kandev.db, -wal, -shm) |
/data/worktrees/, /data/tasks/, /data/repos/, /data/sessions/, /data/lsp-servers/ |
Per-session state |
/data/.npm-global/ |
Agent CLIs installed via npm install -g (NPM_CONFIG_PREFIX) |
/data/home/ |
$HOME for the in-container kandev user — gh CLI and agent CLI auth state |
Persistent agent and gh CLI auth
The image sets HOME=/data/home, so every CLI that writes its auth under $HOME lands on the volume and survives container restarts and image upgrades:
ghCLI —~/.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>/...
A one-time docker exec -it kandev gh auth login (or claude login, codex login, etc.) is enough; you do not need to redo it after docker pull and recreating the container.
The GitHub PAT configured in Settings → Integrations → GitHub is stored as a secret in the database and has always persisted. The
HOME=/data/homesetup covers the separategh auth loginflow that the backend falls back to when noGITHUB_TOKENsecret is set.
Configuration
Configuration is done via KANDEV_-prefixed environment variables:
docker run -p 38429:38429 \
-v kandev-data:/data \
-e KANDEV_LOG_LEVEL=debug \
ghcr.io/kdlbs/kandev:latest
Environment Variables
See configuration.md for the full reference (including the YAML form and every knob the backend reads). The table below covers the env vars most often set in a Docker deployment.
| Variable | Required | Default | Description |
|---|---|---|---|
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_LOG_LEVEL |
No | info |
Log level: debug, info, warn, error |
KANDEV_LOGGING_FORMAT |
No | text |
Log format: text or json |
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. |
KANDEV_DOCKER_ENABLED |
No | false |
Enable Docker runtime for agents (see below) |
File-mode note: when
KANDEV_LOGGING_OUTPUTPATHis a file path, the active log file is created with mode0600(owner read/write only). Run any log shipper or sidecar as the same user, or usestdout/stderrand let the container runtime collect logs.
Upgrading from a pre-
KANDEV_HOME_DIRimage? The SQLite DB path moved from/data/kandev.dbto/data/data/kandev.db. The backend auto-migrates the legacykandev.db(plus any-wal/-shmfiles) on first boot — look forMigrated SQLite database from pre-KANDEV_HOME_DIR locationin the logs. If you prefer to pin the old location instead, set-e KANDEV_DATABASE_PATH=/data/kandev.db. If you previously setKANDEV_DATA_DIR, replace it withKANDEV_HOME_DIR.
PostgreSQL
To use PostgreSQL instead of SQLite:
docker run -p 38429:38429 \
-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
Port
Kandev exposes a single port. The Go backend serves the API, WebSocket, static SPA assets, and page boot data — all on one port:
| Port | Service |
|---|---|
38429 |
API + WebSocket + Web UI |
Override the port:
docker run -p 9080:9080 \
-v kandev-data:/data \
ghcr.io/kdlbs/kandev:latest \
kandev start --backend-port 9080
Docker Compose
Create a docker-compose.yml:
services:
kandev:
image: ghcr.io/kdlbs/kandev:latest
ports:
- "38429:38429"
volumes:
- kandev-data:/data
restart: unless-stopped
volumes:
kandev-data:
docker compose up -d
With PostgreSQL
services:
kandev:
image: ghcr.io/kdlbs/kandev:latest
ports:
- "38429:38429"
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:
Reverse Proxy
Since Kandev serves everything on a single port, a reverse proxy only needs to forward all traffic to port 38429. No extra environment variables are needed — the frontend automatically uses window.location.origin to reach the API.
Docker Compose with Caddy
services:
kandev:
image: ghcr.io/kdlbs/kandev:latest
volumes:
- kandev-data:/data
restart: unless-stopped
caddy:
image: caddy:2
ports:
- "80:80"
- "443:443"
volumes:
- caddy-data:/data
- ./Caddyfile:/etc/caddy/Caddyfile
restart: unless-stopped
volumes:
kandev-data:
caddy-data:
Example Caddyfile:
kandev.example.com {
reverse_proxy kandev:38429
}
Docker-in-Docker (Agent Containers)
By default, KANDEV_DOCKER_ENABLED=false inside the container. To enable Docker-based agent execution, mount the Docker socket:
docker run -p 38429:38429 \
-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.
Upgrading
docker pull ghcr.io/kdlbs/kandev:latest
docker compose up -d # or: docker stop kandev && docker rm kandev && docker run ...
The volume at /data carries over the database, worktrees, npm globals, and $HOME for agent CLIs, so there is no manual migration step.
Upgrading across the
HOME=/data/homechange: if you previously randocker exectogh auth loginor log in to agent CLIs on a pre-HOME=/data/homeimage, that state lived in the ephemeral/home/kandevinside the container and is not carried over. Log in once on the new container and it will persist for all subsequent upgrades. If you want to preserve the old state, copy it onto the volume before recreating the container:docker exec kandev sh -c 'cp -a /home/kandev/. /data/home/ 2>/dev/null || true'
Health Check
The backend exposes a /health endpoint:
curl http://localhost:38429/health
For Docker health checks in compose:
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:38429/health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 15s
Troubleshooting
# 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