Skip to content

Configuration

devctl is driven by YAML. Unknown fields are rejected. version: 1 is required.

Editors and agents: point at the JSON Schema so field names complete.

yaml
# yaml-language-server: $schema=https://raw.githubusercontent.com/amr-m-abdelgawad/devctl/main/schema/devctl.config.schema.json
version: 1

The schema file lives at schema/devctl.config.schema.json. The demo config uses a relative path so it works offline.

Discovery

Walks from the current directory toward the filesystem root:

--config points at a file or a .devctl directory. Repository root is the directory that contains .devctl (or the parent of devctl.yaml).

When the main file lives in .devctl/, modular files merge in:

Service and profile filenames become keys (identity.yaml → service identity). Files within each modular directory are loaded in sorted filename order, making overrides deterministic even when both .yaml and .yml fragments resolve to the same key.

Overlays and precedence

Local overlays merge after the repo config. Each stage overrides the one before it, so the rightmost source wins:

The repository's own config.local.yaml overrides the one in your home directory, not the other way round: overlays are applied home-first so the repo-specific file gets the last word.

TUI appearance is not this file. Theme, keys, mouse, and MCP listen live in tui.json — see Building from source and TUI.

Top-level keys

KeyRole
versionMust be 1
project.nameShown in the TUI header
google.project_id / regionCloud project (optional)
templatesNamed service bases (extends)
servicesProcess definitions
tasksNamed transient commands run with devctl run
profilesNamed service sets + extra env
proxyListen address, token endpoint, routes
logsIn-memory cap and persistence
telemetry.otlpOpt-in loopback OTLP/HTTP+JSON receiver (off by default) — see Telemetry
auth.refresh_threshold_secondsToken refresh window (default 300)
shutdownstop_services_on_exit, grace_seconds
uiOptional theme / keymap hints in YAML (TUI prefs still win from tui.json)
secretsExtra redaction markers and regexes
doctor.toolsExtra CLI binaries to probe
plugins{ path } modules loaded when the supervisor starts
environment.sources / secretsEnv source order and named secrets

Templates

yaml
templates:
  python-http:
    health: { type: http, interval_seconds: 2, timeout_seconds: 1 }
    logs: { stdout: true, stderr: true }
    restart: { policy: on_failure, max_retries: 2, backoff_seconds: 1 }

services:
  api:
    extends: python-http
    command: [python3, main.py]

Tasks

Tasks accept command, shell, working_dir, dependencies, and environment. Dependencies name services and are made ready before the one-off command runs. Tasks have no ports, health checks, restart policy, or status entry; see Services for an example.

Validation and reload

bash
devctl config validate
devctl config validate --json
devctl config show
devctl config diff
devctl reload

config diff explains the resolved result instead of merely printing it. Each entry includes the winning source file and layer (main, modular_service, modular_profile, modular_proxy, home_local, repo_local, or synthesized) and the ordered sources it shadowed. Use --json for structured output.

Checks: YAML syntax, required fields, unknown fields, service references, dependency conditions and cycles, health thresholds, duplicate ports, identities, proxy routes (including per-service proxy fragments merged at load), proxy.listen.port when proxy.enabled is true, environment references, profile references, optional plugins[].path, and telemetry.otlp.listen (loopback host, valid port, no collision with the proxy/token-endpoint/gRPC-route ports).

The TUI Config screen v / /buffer overlay validates this text before writing. Invalid YAML is not saved. e still opens $EDITOR.

The supervisor watches .devctl/ (fs.watch, ~200ms debounce) and runs the same path as /reload. devctl reload and TUI /reload re-read configuration, publish ConfigurationChanged, and list services that must restart because command, environment, ports, identity, or watch changed.

Changing the plugins path list hot-applies token providers, log parsers, and proxy middleware. Editing an already-imported plugin file (same path, newer mtime) still requires devctl down && devctl start — Bun’s module cache cannot unload it. A running service’s environment is unchanged until that service restarts.

Released under the MIT License.