Template Runtime
Build and run DevEx lifecycle templates for repeatable local stacks, services, and automation
Template Runtime
DevEx lifecycle templates are versioned YAML files that describe how to install, inspect, maintain, and remove a stack. They are used by the top-level lifecycle commands:
devex install --template <id-or-path>
devex status --template <id-or-path>
devex maintain --template <id-or-path>
devex remove --template <id-or-path>
devex template plan <id-or-path>Lifecycle templates do not use template apply
Use devex install --template, devex maintain --template, devex status --template, and devex remove --template for runtime templates. The older custom-template packaging commands live under devex template custom.
Schema Reference
Canonical runtime templates use schema_version: devex.template/v1.
schema_version: devex.template/v1
id: example-stack
name: Example Stack
version: 1.0.0
description: Example lifecycle template
inputs: {}
lifecycle:
install: []Required top-level fields:
schema_version: must bedevex.template/v1id: stable template identifiername: display nameversion: template versionlifecycle: lifecycle action map
Optional top-level fields are description, author, homepage, license, tags, categories, inputs, and metadata.
Supported lifecycle actions:
install: create or configure resourcesmaintain: update, restart, repair, or reconcile resourcesstatus: inspect resourcesremove: remove resources created by a previous install
Supported step actions:
app: install or manage an application byref, inline fields, ordefinitionplugin: call a plugin command directlyshell: run a validated executable with explicit argumentsfile: run a file operation such as write, copy, template, or removeservice: run a service action such as start, stop, restart, enable, or disablegroup: a logical dependency boundary; current executors treat it as a no-op
Every step must define exactly one primary action. Common step fields are id, order, if, depends_on, on_error, description, and resources.
Inputs
Supported input types are text, select, multiselect, and bool.
inputs:
databases:
type: multiselect
prompt: Databases to install
required: true
options:
- label: PostgreSQL
value: postgres
- label: Redis
value: redis
postgres_password:
type: text
prompt: PostgreSQL password
sensitive: true
required: trueInput fields:
type: one of the supported input typesprompt: interactive prompt textrequired: fail if empty in non-interactive modesensitive: redact from persisted state and displayed plan outputdefault: template defaultoptions: allowed values forselectormultiselectvalidation:pattern,message,min, andmaxdescription: additional documentation
Input precedence, from lowest to highest:
- Template defaults
- Environment variables named
DEVEX_INPUT_<KEY>, for exampleDEVEX_INPUT_DATABASES - YAML files passed with
--answers path.yaml - CLI values passed with
--answer key=value
Interactive prompts are used only when a required input is still missing and --non-interactive is not set. With --non-interactive, missing required inputs fail immediately.
# prod.answers.yaml
databases:
- postgres
- redis
postgres_name: devex-postgres
postgres_password: change-meDEVEX_INPUT_POSTGRES_NAME=ci-postgres \
devex install --template databases \
--answers prod.answers.yaml \
--answer databases=postgres,redis \
--non-interactiveConditions And Interpolation
Supported condition helpers:
equals(value, expected)contains(list, value)exists(value)platform(os)has_resource(step_id)- boolean operators:
&&,||,!
Supported interpolation references:
{{ inputs.key }}{{ resource.step_id.external_id }}{{ system.os }}{{ system.arch }}
Use if: has_resource("step-id") for maintain, status, and remove steps that operate on resources from a prior install.
State Store
DevEx records lifecycle activity in a local bbolt database. The default path is:
~/.devex/state.dbThe state path can be overridden with:
- config key:
state.db_path - environment variable:
DEVEX_STATE_DB - compatibility environment variable:
DEVEX_STATE_DB_PATH
The store records:
TemplateRun: action, redacted inputs, plan, status, host, platform, timestamps, and errorOperationRecord: each executed step, plugin request, status, outputs, timestamps, and errorManagedResource: created resources, external IDs, installer/plugin metadata, desired/current state, timestamps, and removal metadata
devex runs reads this state:
devex runs list
devex runs show <run-id>
devex runs resources <run-id>
devex runs operations <run-id>Lifecycle Semantics
install creates a run before execution, records every operation before it executes, and persists declared resources after successful steps.
status, maintain, and remove select a recorded install run with --run latest or --run <run-id>. latest is the default for these commands.
remove uses the state store, not only the current template file. If a template file changes after install, DevEx still targets resources recorded by the selected install run. When possible, remove planning augments or synthesizes cleanup steps from recorded resource removal hints.
Use --dry-run on lifecycle commands to display the redacted plan without executing it:
devex maintain --template databases --run latest --dry-run
devex remove --template databases --run latest --dry-run --format yamlPlugin Arguments
Plugin and app steps pass structured arguments instead of a shell string:
plugin:
name: package-manager-docker
command: remove
args:
- "{{ resource.postgres.external_id }}"
- --volumes
env:
EXAMPLE: value
working_dir: /tmpAt runtime, the plugin receives the command first, followed by each item in args. Prefer one argument per YAML list item. Do not encode shell pipelines or multiple commands inside a single argument.
For Docker-style templates, use explicit flags such as --name, --port, --env, --volume, --restart, and --detach.
Sensitive Inputs
Set sensitive: true for passwords, tokens, API keys, private keys, and secret-like values.
Sensitive values may be held in memory long enough to plan and execute. Persisted runs, resources, operation requests, plan snapshots, logs, and metadata redact sensitive values as [REDACTED]. Redaction applies regardless of whether the value came from --answer, --answers, an environment variable, a default, or an interactive prompt.
Database Template Example
schema_version: devex.template/v1
id: databases
name: Databases
version: 1.0.0
description: Local PostgreSQL and Redis containers
inputs:
databases:
type: multiselect
prompt: Databases to install
required: true
options:
- label: PostgreSQL
value: postgres
- label: Redis
value: redis
postgres_name:
type: text
prompt: PostgreSQL container name
default: devex-postgres
postgres_password:
type: text
prompt: PostgreSQL password
sensitive: true
required: true
lifecycle:
install:
- id: docker-engine
order: 10
plugin:
name: package-manager-docker
command: ensure-installed
args:
- --add-user
- id: postgres
order: 20
if: contains(inputs.databases, "postgres")
app:
name: PostgreSQL
category: databases
installer: docker
command: install
args:
- postgres:16
- --name={{ inputs.postgres_name }}
- --port=127.0.0.1:5432:5432
- --env=POSTGRES_PASSWORD={{ inputs.postgres_password }}
- --detach
resources:
- type: container
name: "{{ inputs.postgres_name }}"
external_id: "{{ inputs.postgres_name }}"
status:
- id: postgres-status
order: 10
if: has_resource("postgres")
plugin:
name: package-manager-docker
command: status
args:
- "{{ resource.postgres.external_id }}"
maintain:
- id: postgres-restart
order: 10
if: has_resource("postgres")
plugin:
name: package-manager-docker
command: restart
args:
- "{{ resource.postgres.external_id }}"
remove:
- id: postgres-remove
order: 10
if: has_resource("postgres")
plugin:
name: package-manager-docker
command: remove
args:
- "{{ resource.postgres.external_id }}"
- --volumesRun it non-interactively:
devex template validate ./templates/databases.yaml
devex template plan ./templates/databases.yaml \
--action install \
--answer databases=postgres,redis \
--answer postgres_password=change-me \
--non-interactive
devex install --template ./templates/databases.yaml \
--answer databases=postgres,redis \
--answer postgres_password=change-me \
--non-interactiveGaming Server Template Example
schema_version: devex.template/v1
id: factorio-server
name: Factorio Server
version: 1.0.0
description: Dedicated Factorio server in Docker
inputs:
server_name:
type: text
prompt: Server container name
default: factorio
save_dir:
type: text
prompt: Host save directory
default: ~/.devex/factorio
lifecycle:
install:
- id: factorio
order: 10
app:
name: Factorio Server
category: gaming
installer: docker
command: install
args:
- factoriotools/factorio:stable
- --name={{ inputs.server_name }}
- --port=34197:34197/udp
- --volume={{ inputs.save_dir }}:/factorio
- --restart=unless-stopped
- --detach
resources:
- type: container
name: "{{ inputs.server_name }}"
external_id: "{{ inputs.server_name }}"
status:
- id: factorio-status
order: 10
if: has_resource("factorio")
plugin:
name: package-manager-docker
command: status
args:
- "{{ resource.factorio.external_id }}"
remove:
- id: factorio-remove
order: 10
if: has_resource("factorio")
plugin:
name: package-manager-docker
command: remove
args:
- "{{ resource.factorio.external_id }}"Automation Examples
# Validate and produce a JSON plan in CI
devex template validate ./templates/databases.yaml --format json
devex template plan ./templates/databases.yaml \
--action install \
--answers ./ci.answers.yaml \
--non-interactive \
--format json
# Use isolated state for ephemeral automation
DEVEX_STATE_DB=/tmp/devex-state.db \
devex install --template ./templates/databases.yaml \
--answers ./ci.answers.yaml \
--non-interactive
# Inspect and remove the latest recorded install run
DEVEX_STATE_DB=/tmp/devex-state.db devex runs list --format json
DEVEX_STATE_DB=/tmp/devex-state.db \
devex remove --template ./templates/databases.yaml \
--run latest \
--force \
--non-interactive