Configuration
Configure Kandev workspaces, runtimes, settings, and environment behavior.
Kandev's backend reads configuration from three sources, in this order of precedence (later sources override earlier ones):
- Built-in defaults (
apps/backend/internal/common/config/config.go). - A YAML config file (
config.yaml). - Environment variables (
KANDEV_*).
Both the file and env vars are optional; the backend boots with sensible defaults out of the box. See docker.md and k8s.md for deployment-specific tables; this page is the full reference.
Config file
The backend looks for config.yaml in the following order:
- An explicit path passed at startup (used in tests).
- The current working directory.
/etc/kandev/.
A missing file is not an error - defaults plus env vars take over.
Environment variables
Backend configuration normally uses the KANDEV_ prefix. Nested keys map by replacing . with _ and uppercasing; camelCase becomes one uppercase run (no underscore inserted), because Viper does not synthesize a snake_case form. A small set of explicitly bound compatibility aliases is also supported, including the unprefixed AGENTCTL_PORT.
| YAML key | Env var |
|---|---|
server.port | KANDEV_SERVER_PORT |
server.webInternalUrl | KANDEV_SERVER_WEBINTERNALURL (or alias KANDEV_WEB_INTERNAL_URL) |
database.dbName | KANDEV_DATABASE_DBNAME |
logging.maxSizeMb | KANDEV_LOGGING_MAXSIZEMB |
homeDir | KANDEV_HOME_DIR (alias) |
logging.level | KANDEV_LOG_LEVEL (alias) |
agent.standalonePort | AGENTCTL_PORT or KANDEV_AGENT_STANDALONE_PORT (aliases) |
The aliases on the right are explicit bindings - see LoadWithPath in config.go for the full list. New keys should follow the deterministic rule (KANDEV_<SECTION>_<KEYUPPERCASE>) unless compatibility requires an explicit alias.
Full config.yaml example
Every key shown here has a default - copying the whole file changes nothing. Use it as a starting point and delete what you don't need to override.
# Kandev root directory. Empty = ~/.kandev (or KANDEV_HOME_DIR if set).
# All workspace artifacts (data, tasks, worktrees, repos, sessions) live here.
homeDir: ""
server:
host: "0.0.0.0"
port: 38429 # API + WebSocket + Web UI
readTimeout: 30 # seconds
writeTimeout: 30 # seconds
webInternalUrl: "" # internal URL the backend uses to call the web app
database:
driver: "sqlite" # "sqlite" or "postgres"
path: "" # sqlite: empty = $homeDir/data/kandev.db
# postgres-only fields below (ignored when driver=sqlite)
host: "localhost"
port: 5432
user: "kandev" # required when driver=postgres
password: "" # required when driver=postgres in most setups
dbName: "kandev" # required when driver=postgres
sslMode: "disable" # disable | require | verify-ca | verify-full
maxConns: 25
minConns: 5
nats:
url: "" # empty = use in-memory event bus
clusterId: "kandev-cluster"
clientId: "kandev-client"
maxReconnects: 10
events:
namespace: "" # empty = derive from runtime data identity
docker:
enabled: true # disables Docker-based executors when false
host: "" # empty = platform default (unix:///var/run/docker.sock, etc.)
apiVersion: "" # empty = auto-negotiate
tlsVerify: false
defaultNetwork: "kandev-network"
volumeBasePath: "" # empty = /var/lib/kandev/volumes (Linux/macOS)
agent:
standaloneHost: "localhost"
standalonePort: 39429 # agentctl control port
auth:
jwtSecret: "" # empty = auto-generate a random dev secret on boot
tokenDuration: 3600 # seconds
logging:
level: "info" # debug | info | warn | error
format: "text" # accepted values: text | json; omit for environment-based default
outputPath: "stdout" # stdout | stderr | /path/to/file.log
# Rotation - only applied when outputPath is a file path.
# Active log files are created with mode 0600 (owner-only).
maxSizeMb: 100 # rotate at this size; 0 = lumberjack default (100MB)
maxBackups: 5 # 0 = unlimited
maxAgeDays: 30 # 0 = unlimited
compress: true # gzip rotated files
repositoryDiscovery:
roots: [] # absolute paths to scan for local git repos
maxDepth: 5
worktree:
enabled: true
defaultBranch: "main"
cleanupOnRemove: true
fetchTimeoutSeconds: 60
pullTimeoutSeconds: 60
repoClone:
basePath: "" # empty = $homeDir/repos
debug:
pprofEnabled: false # enables /debug/pprof and /api/v1/debug/memoryRequired vs optional
Almost every field has a default and is optional. The exceptions:
| Field | When required | What happens otherwise |
|---|---|---|
database.user | database.driver=postgres | Startup fails with database.user is required for postgres driver |
database.dbName | database.driver=postgres | Startup fails with database.dbName is required for postgres driver |
database.password | database.driver=postgres in most setups (some Postgres configs allow passwordless local auth) | Connection fails at runtime |
auth.jwtSecret | Never strictly required, but in production set an explicit value | A random secret is generated on boot - tokens become invalid on restart |
Validated value sets (any other value is a startup error):
| Field | Allowed values |
|---|---|
server.port | 1-65535 |
database.driver | sqlite, postgres |
database.port | 1-65535 (only validated when driver=postgres) |
database.sslMode | disable, require, verify-ca, verify-full (only validated when driver=postgres) |
logging.level | debug, info, warn, error |
logging.format | json, text |
Tips
- Env vars override the file. Useful for secrets (
KANDEV_DATABASE_PASSWORD) and per-environment knobs (KANDEV_LOG_LEVEL). - K8s / Docker: prefer env vars for everything; skip the YAML file entirely.
- Local dev: drop a
config.yamlnext to where you run the backend; viper picks it up from the current working directory. - Format auto-detection: when
logging.formatis omitted, Kandev defaults tojsonifKANDEV_ENVisproduction/prodorKUBERNETES_SERVICE_HOSTis set; otherwise it defaults totext. The literal valueautois not accepted.