Configuration
devflow is configured by a committed file plus environment variables. All sections are optional — an empty .devflow.yml is valid.
File formats: .devflow.yml / .devflow.yaml (full-featured YAML) or a lightweight devflow.toml / .devflow.toml — both parse into the same schema. devflow init and the GUI currently write YAML.
Hierarchy
Section titled “Hierarchy”Merged from three sources, highest precedence first:
| Priority | Source | Purpose |
|---|---|---|
| 1 | Environment variables | quick toggles, CI overrides, secrets |
| 2 | .devflow.local.yml | machine-specific overrides (gitignore it) |
| 3 | .devflow.yml / devflow.toml | team-shared config (committed) |
devflow config -v shows the effective config with per-value provenance. Machine-local state (registered workspaces, parents, worktree paths) lives separately in ~/.config/devflow/local_state.yml.
name: my-project # optional; defaults to the main repo root's directory nameUsed as {repo} in worktree templates, in container/database naming, and as the proxy project name.
git: auto_create_on_workspace: true # provision services for manually added worktrees main_workspace: main # auto-detected on init workspace_filter_regex: "^feature/.*" # only branches matching this pattern exclude_workspaces: [main, master] # never provision these (supports * globs)For Git, the branch checked out in the physical primary checkout is the default workspace; initialization refuses a detached primary checkout. This keeps the configured root aligned with Git’s actual worktree graph instead of guessing from origin/HEAD or branch-name conventions.
These toggles apply to installed VCS-hook handling for materialized worktrees. Ordinary in-place git checkout no longer provisions devflow environments; use devflow switch, or create a linked worktree manually and let the hook adopt it.
Opt-in provisioning with a branch marker
Section titled “Opt-in provisioning with a branch marker”By default every adopted worktree gets services. To make hook-driven provisioning opt-in instead, set a marker pattern:
git: workspace_filter_regex: "df_" # unanchored regex — matches anywhere in the branch nameNow git worktree add ../repo.df_login df_login provisions databases, while a scratch worktree like quickfix is adopted without services (worktree files are still copied). The filter only gates the automatic path: explicit commands — devflow switch -c <branch>, devflow service create — always provision, whatever the branch is called.
The pattern is a search regex matched against the raw branch name, so anchor it (^df/) if the marker must be a prefix. An invalid regex fails closed — nothing auto-provisions — and devflow doctor reports it. workspace_filter_regex also accepts the legacy spellings branch_filter_regex, auto_create_workspace_filter, and auto_create_branch_filter.
behavior
Section titled “behavior”behavior: max_workspaces: 10 # default retention for `devflow service cleanup`services
Section titled “services”An array of named services; each picks a provider (type) and an engine (service_type). Common fields:
services: - name: app-db # unique name (used by -s and {{ service['app-db'] }}) type: local # local | shared | neon | dblab | xata service_type: postgres # postgres | clickhouse | mysql | redis | rustfs | generic | plugin auto_workspace: true # follow git branching (default true) default: true # default target when -s is omittedlocal: (PostgreSQL via CoW containers)
Section titled “local: (PostgreSQL via CoW containers)” local: image: postgres:17clickhouse: / mysql: (CoW containers)
Section titled “clickhouse: / mysql: (CoW containers)” clickhouse: image: clickhouse/clickhouse-server:latest port_range_start: 59000 # HTTP port (native = HTTP + 877) data_root: ~/.local/share/devflow user: default password: "" mysql: image: mysql:8 port_range_start: 53306 data_root: ~/.local/share/devflow root_password: dev database: myapp user: dev password: devgeneric: (any Docker image)
Section titled “generic: (any Docker image)” generic: image: redis:7-alpine port_mapping: "6379:6379" # fixed mapping, or: port_range_start: 56000 # dynamic allocation environment: { KEY: value } volumes: ["/data/redis:/data"] command: "redis-server --save 60 1" healthcheck: "redis-cli ping"shared: (one global engine, logical isolation)
Section titled “shared: (one global engine, logical isolation)” - name: app-db type: shared service_type: postgres # postgres | clickhouse | redis | rustfs shared: image: postgres:17 # engine image port: 5432 # fixed well-known port template_branching: true # postgres only: CREATE DATABASE … TEMPLATE parent user: rustfsadmin # rustfs: access key password: rustfsadmin # rustfs: secret keyRedis supports separate workspace containers (type: local with a generic container configuration) or a shared engine (type: shared, up to 15 allocated workspace databases). Legacy local Redis configurations without a generic block retain shared allocation during migration. RustFS (service_type: rustfs, aliases s3/objectstorage) serves S3 on 9000 with a bucket per workspace.
processes
Section titled “processes”Workspace-scoped project processes — app servers, frontend dev servers, background workers, schedulers — run directly on the machine without Docker. Process env values use the same MiniJinja context as hooks, so service connection URLs are available as {{ service['name'].url }}.
processes: provider: native start_policy: config # or inherit_parent auto_start: true # start after devflow switch aligns services and hooks auto_stop: true # stop before devflow remove deletes the workspace daemons: api: run: "npm run dev" dir: "." # relative to workspace/worktree root depends: [] port: { expect: [3000], bump: 50 } ready_http: "http://127.0.0.1:3000/health" watch: ["src/**/*.ts", "package.json"] # devflow daemon restarts on changes env: DATABASE_URL: "{{ service['app-db'].url }}" worker: run: "npm run worker" required: false # optional; failures do not fail switch/process start depends: [api] ready_delay: 2 stop_timeout: 10Daemon fields:
| Field | Purpose |
|---|---|
run | shell command to execute |
dir | working directory relative to the workspace root, or absolute |
env | environment variables; values are templates |
required | defaults to true; set false for optional processes whose failures should not fail lifecycle commands |
depends | process names that start first |
port | a port number, port array, or `{ expect: […], bump: true |
ready_delay | seconds to wait before readiness checks begin, or before considering ready when no other check is configured |
ready_port | TCP readiness check |
ready_http | HTTP 2xx readiness check (ports are remapped when port.bump changes them) |
ready_cmd | shell command readiness check |
ready_output | regex matched against captured stdout/stderr logs |
ready_timeout | readiness timeout in seconds (default 60) |
stop_timeout | graceful shutdown timeout before SIGKILL (default 3, Unix) |
shutdown_signal | graceful Unix signal: TERM, INT, HUP, QUIT, or KILL |
watch | glob patterns, relative to dir, that the controller daemon polls for restart-on-change |
retry | number of controller-daemon restart attempts after a crash |
Manage them with devflow process start|stop|restart|status|logs. Auto-started process commands reuse hook approvals; pre-approve with devflow hook approvals add "npm run dev" for non-interactive automation, or set DEVFLOW_APPROVE_HOOKS=1. The native supervisor records desired state and runtime observations. devflow proxy reads process state and exposes port-backed processes as https://<process>.<workspace>.<project>.<suffix> (default suffix .localhost). Run devflow daemon start to keep desired-state/watch/retry reconciliation active in the background. See Project processes for migration examples, readiness checks, and operational commands.
Cloud providers (experimental)
Section titled “Cloud providers (experimental)” neon: { api_key: ${NEON_API_KEY}, project_id: ${NEON_PROJECT_ID}, base_url: https://console.neon.tech/api/v2 } dblab: { api_url: https://dblab.example.com, auth_token: ${DBLAB_TOKEN} } xata: { api_key: ${XATA_API_KEY}, organization_id: my-org, project_id: my-project, base_url: https://api.xata.tech }plugin:
Section titled “plugin:” plugin: name: my-plugin # resolved as devflow-plugin-my-plugin on PATH # path: /usr/local/bin/my-plugin timeout: 30 config: { region: us-east-1 } # opaque JSON forwarded to the pluginworktree
Section titled “worktree”worktree: path_template: "../{repo}.{workspace}" # {repo}, {workspace} (collision-safe key), {branch} legacy copy_files: [.env.local, .env] # files/dirs reflink-copied from main copy_ignored: false # deprecated; use fingerprinted caches copy_ai_configs: false # deprecated; use explicit copy_files extra_ai_dirs: [] # additional AI tool dirsFull semantics in Worktrees. Worktrees are the only Git workspace model; an old worktree.enabled key is accepted for compatibility but ignored with a deprecation warning — it can no longer restore in-place checkout behavior. {workspace} is the collision-safe service_key, while user-facing and VCS operations keep the raw workspace name.
hooks: <phase>: # post-create, post-switch, pre-commit, … or any custom name <hook-name>: "command" # simple form <hook-name>: # extended form command: "npm run migrate" working_dir: "./backend" condition: "file_exists:package.json" continue_on_error: false background: false environment: { NODE_ENV: development } <hook-name>: # action form action: type: write-env # write-env | write-file | copy | replace | docker-exec | http | notify | shell path: .env.local vars: { DATABASE_URL: "{{ service['app-db'].url }}" }Phases, variables, filters, conditions, actions, and recipes: hooks reference.
execute
Section titled “execute”execute: multiplexer: tmux # or zellij (auto-detected when unset) detach_command: "screen -dmS {session} bash -c {cmd}" # custom launcher: {session} {dir} {cmd}Used by devflow switch -o/--open and -d/--detach.
commit
Section titled “commit”commit: generation: command: "claude -p --model haiku" # external CLI (preferred)Agent guides
Section titled “Agent guides”Use devflow agent context and devflow agent skill --target all. The retired agent.auto_context setting is ignored with a migration warning.
Value interpolation
Section titled “Value interpolation”Secrets in service configs support ${ENV_VAR} interpolation, resolved at runtime:
neon: api_key: ${NEON_API_KEY}Workspace identity
Section titled “Workspace identity”The raw VCS name is canonical for display, registry lookup, and VCS operations. Services and generated paths use a separate deterministic service_key: already-safe names are preserved, while names requiring normalization include a stable short hash. Consequently names such as feature/auth, feature-auth, and case variants remain distinct. Hook templates expose the key as workspace_key; workspace_sanitized is an alias.
Use project_id for a stable data owner across repository moves. Shared legacy adoption uses shared.legacy_resources; local PostgreSQL adoption uses local.legacy_project_id. See Data ownership and recovery before changing an existing installation.