Kandev Docs

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):

  1. Built-in defaults (apps/backend/internal/common/config/config.go).
  2. A YAML config file (config.yaml).
  3. 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 keyEnv var
server.portKANDEV_SERVER_PORT
server.webInternalUrlKANDEV_SERVER_WEBINTERNALURL (or alias KANDEV_WEB_INTERNAL_URL)
database.dbNameKANDEV_DATABASE_DBNAME
logging.maxSizeMbKANDEV_LOGGING_MAXSIZEMB
homeDirKANDEV_HOME_DIR (alias)
logging.levelKANDEV_LOG_LEVEL (alias)
agent.standalonePortAGENTCTL_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/memory

Required vs optional

Almost every field has a default and is optional. The exceptions:

FieldWhen requiredWhat happens otherwise
database.userdatabase.driver=postgresStartup fails with database.user is required for postgres driver
database.dbNamedatabase.driver=postgresStartup fails with database.dbName is required for postgres driver
database.passworddatabase.driver=postgres in most setups (some Postgres configs allow passwordless local auth)Connection fails at runtime
auth.jwtSecretNever strictly required, but in production set an explicit valueA random secret is generated on boot - tokens become invalid on restart

Validated value sets (any other value is a startup error):

FieldAllowed values
server.port1-65535
database.driversqlite, postgres
database.port1-65535 (only validated when driver=postgres)
database.sslModedisable, require, verify-ca, verify-full (only validated when driver=postgres)
logging.leveldebug, info, warn, error
logging.formatjson, 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.yaml next to where you run the backend; viper picks it up from the current working directory.
  • Format auto-detection: when logging.format is omitted, Kandev defaults to json if KANDEV_ENV is production/prod or KUBERNETES_SERVICE_HOST is set; otherwise it defaults to text. The literal value auto is not accepted.

On this page