DevEx LogoMy App
Configuration

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 be devex.template/v1
  • id: stable template identifier
  • name: display name
  • version: template version
  • lifecycle: lifecycle action map

Optional top-level fields are description, author, homepage, license, tags, categories, inputs, and metadata.

Supported lifecycle actions:

  • install: create or configure resources
  • maintain: update, restart, repair, or reconcile resources
  • status: inspect resources
  • remove: remove resources created by a previous install

Supported step actions:

  • app: install or manage an application by ref, inline fields, or definition
  • plugin: call a plugin command directly
  • shell: run a validated executable with explicit arguments
  • file: run a file operation such as write, copy, template, or remove
  • service: run a service action such as start, stop, restart, enable, or disable
  • group: 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: true

Input fields:

  • type: one of the supported input types
  • prompt: interactive prompt text
  • required: fail if empty in non-interactive mode
  • sensitive: redact from persisted state and displayed plan output
  • default: template default
  • options: allowed values for select or multiselect
  • validation: pattern, message, min, and max
  • description: additional documentation

Input precedence, from lowest to highest:

  1. Template defaults
  2. Environment variables named DEVEX_INPUT_<KEY>, for example DEVEX_INPUT_DATABASES
  3. YAML files passed with --answers path.yaml
  4. 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-me
DEVEX_INPUT_POSTGRES_NAME=ci-postgres \
  devex install --template databases \
  --answers prod.answers.yaml \
  --answer databases=postgres,redis \
  --non-interactive

Conditions 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.db

The 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 error
  • OperationRecord: each executed step, plugin request, status, outputs, timestamps, and error
  • ManagedResource: 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 yaml

Plugin 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: /tmp

At 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 }}"
          - --volumes

Run 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-interactive

Gaming 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

On this page