Available builtins
Builtins are engine-internal Go functions invoked from a step via type: builtin. They run in-process with access to the merged config and the same registry used by type: builtin declarative commands.
Predicate builtins as step bodies (assertion semantics)
Section titled “Predicate builtins as step bodies (assertion semantics)”A predicate builtin — one that answers a yes/no question about the world (file_exists, executable_in_path, tcp_reachable, http_check, containers_running, env_keys_present, config_keys_present, and the shell builtin) — may be used directly as a step body, not only inside a check: block. Used as a body, a predicate is an assertion:
- A true result (the check passes) makes the step succeed.
- A false result (the check fails) fails the step with the predicate’s own message, halting the pipeline like any other step failure. No new error type is introduced — the predicate’s explanation becomes the step error.
Predicate-body steps are always re-run: deploy’s “already up-to-date” gate and per-step action-hash skip never skip a step whose body is a predicate (the same always-run treatment check: steps get). An assertion has no meaningful cached result, so it re-evaluates on every deploy.
A when: guard still applies as normal — a predicate-body step with a when: that evaluates false is skipped without asserting (a conditional assertion stays conditional). The guard itself, however, takes none of these builtins: when: {type: builtin} resolves against the separate predicate registry (dir-exists, file-missing, …) and never against the builtins on this page — the two are disjoint registries.
- name: assert-app-reachable type: builtin cmd: http_check with: url: http://localhost:8080/health status: 200 contains: '"ok"' retries: 10 interval: 2sThis capability is purely permissive: predicates that previously were legal only inside check: are now also legal as bodies, in every pipeline (deploy.yml, reset.yml, lifecycle.yml, and test scenarios) and as type: builtin user commands. Existing configs are unaffected.
Action builtins in check: — permitted, but a bad idea
Section titled “Action builtins in check: — permitted, but a bad idea”The schema also allows the reverse: an action builtin (source_clone, remove_paths, service_configs_render, …) is accepted in a check: position, because some actions are read-only (service_configs_check) and rejecting the whole kind would break them. That permission is not an endorsement — a check: answers “is this already done?” and is re-evaluated by the engine at times a step body is not, so putting a mutating builtin there makes the answer change the world it is being asked about. Keep check: to predicates and read-only actions. workspace/validate.yml enforces this on its own surface with a fixed allowlist (shell, file_exists, executable_in_path, env_keys_present, tcp_reachable, http_check, config_keys_present) — see validate.md.
Contents
Section titled “Contents”- Catalogue
service_dirs_ensureservice_configs_renderservice_configs_render_checkservice_generated_harvestservice_configs_copyservice_configs_checkmessageconfirmdocker_remove_project_volumesdocker_wait_healthycontainers_runninghttp_checkremove_pathssource_clone- Internal engine builtins (not callable from user YAML)
- Naming convention
Catalogue
Section titled “Catalogue”| Builtin | Purpose |
|---|---|
service_dirs_ensure | Create service hub directories |
service_configs_render | Render config files from a template pack into the service hub (replays generated values) |
service_configs_render_check | Verify rendered config targets exist; pairing it as a check: re-runs the render every deploy |
service_generated_harvest | Harvest the service’s generated: fields into the generated-value store (write-if-absent) |
service_configs_copy | ⚠️ Deprecated — copy template config files into the service hub |
service_configs_check | ⚠️ Deprecated — verify that copied config files exist in the service hub |
message | Print a styled message at info/success/warning/error level |
confirm | Interactive Y/n prompt (skipped under --yes) |
docker_remove_project_volumes | Remove all volumes whose name is prefixed with the compose project name |
docker_wait_healthy | Wait for Docker containers to reach healthy state |
containers_running | Fast “is running” check for named services or the whole project (no polling, no timeout, no healthcheck required) |
http_check | Assert an HTTP endpoint returns an expected status (and optional body substring), with retries |
remove_paths | Delete project-relative paths from the filesystem |
source_clone | Clone a git repository into a project-relative directory, once (idempotent) |
service_dirs_ensure
Section titled “service_dirs_ensure”Creates service hub directories.
| Parameter | Type | Default | Description |
|---|---|---|---|
service | string | required | Service folder name from workspace/services/<name>/ |
mode | string | skip | skip, error, or recreate |
Resolved dir list: [src] + ServiceConfig.Dirs (from the service’s service.yml). Each entry must be a non-empty relative path that does not escape the service dir.
The configs/ directory is not in this list — service_configs_copy creates it lazily when a configs: block is declared on the service. If you need it created eagerly (or wiped under recreate), add configs to dirs: explicitly.
Mode behavior:
| Mode | Dir missing | Dir exists | Non-dir at path |
|---|---|---|---|
skip | create | no-op | error |
error | create | error | error |
recreate | create | remove + create | error |
Safety: src always uses skip semantics in recreate mode (never removes source code). All other dirs — including configs if listed in dirs: — are wiped under recreate.
service_configs_render
Section titled “service_configs_render”Renders a service’s config files from a config template pack (workspace/templates/config/<pack>/) into the service hub dir (svc.Dir), replaying any harvested ${generated.<name>} values from the generated-value store (.dwe/generated.yml). This is the render-based successor to the deprecated service_configs_copy.
| Parameter | Type | Default | Description |
|---|---|---|---|
service | string | required | Service key |
mode | string | replace | Only replace (overwrite) is supported |
Config rendering is opt-in: a service with no resolvable config pack is a no-op. Pair this step with a service_configs_render_check check: so it re-runs every deploy. See render config for the substrate, pack resolution, and full deploy flow.
- name: render-configs type: builtin cmd: service_configs_render with: service: main check: type: builtin cmd: service_configs_render_check with: service: mainservice_configs_render_check
Section titled “service_configs_render_check”Verifies that every config-pack render target for the service exists on disk. Its primary purpose is structural: pairing it as the check: of a service_configs_render step forces that step to re-run on every deploy (the hasCheck → Run lever in journal/decision.go bypasses the action-hash skip), so template edits and store clears always take effect — exactly mirroring service_configs_copy + service_configs_check.
| Parameter | Type | Default | Description |
|---|---|---|---|
service | string | required | Service key |
A service with no resolvable config pack has nothing to check and is a no-op. Returns an error listing any missing rendered files, which fails the check.
service_generated_harvest
Section titled “service_generated_harvest”Reads each of the service’s declared generated: fields from its on-disk file, extracts the value via the field’s regex (capture group 1), and write-if-absent stores it into the generated-value store (.dwe/generated.yml). The store is saved atomically when a new value is written.
| Parameter | Type | Default | Description |
|---|---|---|---|
service | string | required | Service key |
“Harvest, not mint”: the service’s own generator (e.g. php artisan key:generate) writes the secret; DWE only reads it back and replays it on later renders. Write-if-absent means a value already in the store is preserved, so a redeploy is a no-op. A service with no generated: fields is a no-op. A missing file, a pattern that matches no line, a pattern with no capture group, or a captured empty value are surfaced as errors — never silently skipped. See the generated block and render config.
service_configs_copy
Section titled “service_configs_copy”⚠️ Deprecated. Superseded by
service_configs_render+service_generated_harvest. It keeps working butdwe validateemits a warning and a single runtime deprecation notice fires per copy step. See render config for the replacement.
Copies template config files from configs/services/<service>/ into services/<service>/configs/. Creates the destination configs/ directory if it does not exist — this is the canonical path for configs/ creation (the service_dirs_ensure builtin does not create it).
| Parameter | Type | Default | Description |
|---|---|---|---|
service | string | required | Service key |
mode | string | replace | default, update, or replace |
Mode behaviour:
| Mode | Dest missing | Dest exists |
|---|---|---|
default | write | no-op |
replace | write | overwrite unconditionally |
update | write | merge new KEY=VALUE lines without changing existing keys (env-file aware) |
When the corresponding configs[] entry has a mountpoint, the builtin also touches an empty file at <service-dir>/<mountpoint> so Docker Desktop virtiofs can place a nested file bind mount over it.
service_configs_check
Section titled “service_configs_check”⚠️ Deprecated. The
check:companion of the deprecatedservice_configs_copy. For render-based configs useservice_configs_render_checkinstead.
Verifies that all template config files declared in the service’s service.yml exist in the service hub after a service_configs_copy step. Use as a check: action to assert that configs were successfully deployed.
| Parameter | Type | Default | Description |
|---|---|---|---|
service | string | required | Service key |
Returns an error listing any missing files, which causes the check to fail. Typically used as the post-check of a service_configs_copy step:
- name: copy-configs type: builtin cmd: service_configs_copy with: service: main mode: replace check: type: builtin cmd: service_configs_check with: service: mainmessage
Section titled “message”Prints a message to deploy output.
| Parameter | Type | Description |
|---|---|---|
level | string | info, success, warning, or error (required) |
text | string | Message text (required); supports Go template expressions against DweConfig |
- name: done type: builtin cmd: message with: level: success text: "Deploy of {{ .Project.Name }} completed"confirm
Section titled “confirm”Prompts the user for confirmation before continuing. Skipped when --yes flag is set or when the step is being confirmed via ExecContext.SkipConfirm. On a TTY uses huh.Confirm; on a piped/CI stdin falls back to plain Y/n.
| Parameter | Type | Default | Description |
|---|---|---|---|
message | string | Are you sure? | Prompt text |
ok_msg | string | Continuing | Success message after confirmation |
stop_msg | string | Aborted | Error message after rejection / Esc |
docker_remove_project_volumes
Section titled “docker_remove_project_volumes”Removes all Docker volumes whose name starts with <project_name>_ (resolved from docker.yml against the merged config). No parameters. Aborts if the resolved project name is empty.
docker_wait_healthy
Section titled “docker_wait_healthy”Waits for Docker containers to reach a healthy state. Polls the active Docker Compose stack until all specified containers become healthy, or until the timeout elapses.
| Parameter | Type | Default | Description |
|---|---|---|---|
timeout | string duration | 60s | Maximum time to wait; must be positive (e.g. 120s, 2m) |
interval | string duration | 2s | Poll interval; must be positive |
services | list of strings | all | Restrict to specific compose service names; default = all containers in the active stack |
Example: wait for all containers
- name: wait type: builtin cmd: docker_wait_healthy with: timeout: 120s interval: 2sExample: wait for specific services
- name: wait-app type: builtin cmd: docker_wait_healthy with: timeout: 60s interval: 1s services: - app-main - dbBehavior:
- If no containers are found in the active stack (or matching the service filter), logs a warning and returns success. Idempotent for pipelines that run before
up. - If a container is
unhealthyor the timeout elapses before all containers becomehealthy, returns an error and stops the pipeline. - Containers with no healthcheck (status
none) are treated as always healthy and skipped. - The active stack is determined by the current overlay set (default, enabled services, enabled tools). This builtin respects
ComposeFiles(), notComposeFilesAll().
containers_running
Section titled “containers_running”Fast “is running” check for compose services. Unlike docker_wait_healthy it does not poll for readiness, does not honour a timeout, and does not require services to declare a healthcheck. With a services list, a docker compose ps --status=running --services call returns the set of currently-running services, and the builtin compares that set with the requested list. Without one it checks the whole compose project — see Whole-project mode.
A transient probe failure — the docker compose ps call itself erroring (any non-nil failure to run, e.g. a non-zero exit right at the docker up --wait boundary when the compose CLI / daemon is momentarily busy even though every container is already up) — is retried a bounded number of times with a short backoff before the step fails; a cancelled context is the sole exception and short-circuits the remaining retries immediately. This is not readiness polling: a probe that succeeds but reports a service as not-running fails on the first attempt. When every retry fails, the underlying docker compose ps stderr is surfaced in the error, so the failure is diagnosable. The same retry wraps every probe of the whole-project mode.
| Parameter | Type | Default | Description |
|---|---|---|---|
services | list of strings | — | Compose service names that must be currently running. Absent or empty selects the whole-project mode. An empty-string element is rejected. |
Example: gate a pipeline step on a running service
- name: stack-up type: dwe cmd: "docker up" check: type: builtin cmd: containers_running with: services: [app-main, db]When to choose this over docker_wait_healthy:
- The service has no healthcheck —
docker_wait_healthywould either skip it (treated as healthy) or hang waiting for a status that never arrives. - You need a precondition for a follow-up step (e.g.
service_exec) and want a clear “container X is not running” error instead of a compose stderr trace. - The pipeline runs immediately after
docker upand you just want to confirm the stack came up, without paying a polling round-trip.
With a non-empty services list, a listed service that is not running fails the builtin with services not running: <comma-separated list>.
Whole-project mode
Section titled “Whole-project mode”With no services (absent or []) the builtin checks the compose project as a whole. This is the check: the up step of the built-in deploy pipeline carries:
- name: up type: dwe cmd: "docker up --wait" check: type: builtin cmd: containers_runningIt passes when every non-one-off container of the project is running or has exited with code 0 (a finished init or migrate one-shot). That tolerance is for a one-shot other services reach through depends_on: {condition: service_completed_successfully} or that is scaled to 0 — a one-shot nothing depends on already fails docker compose up --wait itself, before this check runs. compose run containers — the containers of type: daemon commands among them — are not evaluated.
- The expected set is the containers that exist, not the service list of the compose config. A
scale: 0/deploy.replicas: 0service has no container and never fails the check, and a project with no containers at all passes — nothing should be running. - Only services active in the current compose config count. A container that is neither running nor exited 0 (exited non-zero, stuck in
createdorrestarting) fails the step withcontainers not running: <comma-separated container names>when its service is indocker compose config --services. The stopped container of an inactive-profile service, whichup --remove-orphansdoes not remove, and an orphan container are ignored. - Project name. The label filter uses the name dwe passes as
-p(project.name, orproject_nameindocker.yml). When neither is set, the name compose picks itself (COMPOSE_PROJECT_NAME, a top-levelname:, the directory name) is read fromdocker compose config --format json; when that yields nothing, the step fails withcontainers_running: cannot determine the compose project name — set project.name or docker.yml project_name.
Known edges, documented rather than handled:
- podman-compose without the
com.docker.compose.oneofflabel. The builtin detects a backend that does not stamp the label and evaluates every project container unfiltered, so a stoppedcompose runcontainer (a stopped daemon) of an active service can be reported as not running. - A backend without
docker compose config --format jsonorconfig --services. The step fails with the backend’s error when the project name is unset, or when a container is found that is neither running nor exited 0. args.globalis not passed to these compose calls. With--project-directoryor--env-fileinargs.global, the fallback name can differ from the oneupused — setproject.name. With--profilethere, that profile’s services look inactive, so a crashed container of such a service passes instead of failing (never the reverse).
http_check
Section titled “http_check”Predicate builtin (KindPredicate) that performs an HTTP GET and asserts the response. It reports success when the endpoint returns the expected status code and — when contains: is set — a body that includes the given substring. On failure it retries up to retries times, waiting interval between attempts; each individual attempt is bounded by timeout.
| Parameter | Type | Default | Description |
|---|---|---|---|
url | string | required | Target URL. Must parse as an absolute http/https URL with a host. |
status | int | 200 | Expected HTTP status code. |
contains | string | — | Optional substring that must appear in the response body. When empty, the body is not read. |
retries | int | 0 | Additional attempts after the first on mismatch/error. Total attempts = retries + 1. Must be >= 0. |
interval | string duration | 1s | Wait between attempts. Must be >= 0. Cancellable via context. |
timeout | string duration | 5s | Per-attempt timeout (not total). Must be > 0. |
Used as a step body it is an assertion: a passing check succeeds the step, a failing check fails the pipeline with a message like http_check http://localhost:8080/health: expected status 200, got 503 (after 31 attempts). It can equally be used inside a check: block or as a validate.yml check entry.
Example: wait for a health endpoint after up
- name: wait-app-http type: builtin cmd: http_check with: url: http://localhost:8080/health status: 200 contains: '"status":"ok"' retries: 30 interval: 2s timeout: 3sBehavior:
- Attempts run in sequence: attempt → on mismatch/error wait
interval→ next attempt, up toretries + 1total attempts. - A non-
statusresponse, a body missing thecontainssubstring, a connection refusal, or a per-attempt timeout all count as a failed attempt. intervalwaits and per-attempt requests honour context cancellation, so an interrupted pipeline stops promptly.- Invalid params (missing/malformed
url, negativeretries/interval, non-positivetimeout) are rejected at plan time byValidate, before the pipeline runs.
remove_paths
Section titled “remove_paths”Removes paths from the filesystem.
| Parameter | Type | Description |
|---|---|---|
paths | list of strings | Project-relative paths to remove. Each must be relative and must not escape the project root; absolute paths and .. traversal are rejected at validate time. |
source_clone
Section titled “source_clone”Clones a git repository into a project-relative directory. The idempotency gate is built in, so the step needs no when:/check: pair of its own.
| Parameter | Type | Description |
|---|---|---|
repo | string | required. Anything git clone accepts (SSH remote, HTTPS URL, local path). |
dir | string | required. Destination, relative to the project root. Absolute paths, ., and .. traversal are rejected at validate time; a symlinked path component is rejected at run time. |
branch | string | optional. Passed as git clone --branch <branch>. |
Behavior by destination state:
| Destination | Result |
|---|---|
contains a .git entry | skip with a message, success — regardless of which branch that checkout is on |
| absent or an empty directory | clone |
| non-empty and not a git checkout | error naming the path |
| exists but is not a directory | error naming the path |
The skip is deliberately branch-blind: source_clone materialises the source once and never re-points or updates an existing working tree. Switching branches or pulling is the developer’s job (or a separate step).
Git runs with prompting disabled — GIT_TERMINAL_PROMPT=0, an emptied GIT_ASKPASS/SSH_ASKPASS, and GIT_SSH_COMMAND=ssh -o BatchMode=yes when the environment does not already set it to an actual command — so a missing credential fails the step instead of hanging a deploy on an unanswerable prompt. An explicitly set GIT_SSH_COMMAND (custom identity file, port) is honoured as-is; an empty one carries no command git could run, so it counts as unset and takes the default. The command reads no stdin.
- name: clone-backend type: builtin cmd: source_clone with: repo: "${vars.source.backend.repo}" dir: services/backend/src branch: "${vars.source.backend.branch}"Internal engine builtins (not callable from user YAML)
Section titled “Internal engine builtins (not callable from user YAML)”The following builtins are reserved for the engine. They cannot appear in user-authored deploy.yml, reset.yml, or lifecycle.yml files — attempting to use them produces a load-time error.
| Builtin | Description |
|---|---|
docker_daemon_start | Start a named daemon container via docker compose run -d. Invoked by the .start virtual command generated from type: daemon commands. |
docker_daemon_logs | Tail daemon container logs in the foreground. Invoked by the .logs virtual command generated from type: daemon commands. |
docker_daemon_stop | Stop a named daemon container (idempotent). Invoked by the .stop virtual command generated from type: daemon commands. |
docker_stop_remove_container | Stop and remove a named container (docker stop + docker rm -f, both idempotent on missing container). Invoked by the synthetic container phase that dwe reset run --service <name> prepends to every per-service reset pipeline. Params: container_template (string, required; name template, resolved via the project prefix), stop_timeout (duration string, optional, default 10s). On stop failure, propagates the error and does NOT attempt removal. |
daemons_reap | Stop all project daemon containers. Auto-injected by the engine as the _auto_reap_daemons phase at the start of every stop lifecycle pipeline. |
Naming convention
Section titled “Naming convention”docker_* builtins are Docker-specific; service_* builtins operate on per-service folders; unprefixed names are generic. The internal builtins follow the same docker_* / unprefixed pattern.