Перейти к содержимому

Справочник полей сервиса

Каждое поле, допустимое в workspace/services/<name>/service.yml, плюс вложенные блоки (ports, hosts, icon, info, configs, dirs, cli, status, render, generated).

Терминология host vs internal: Поля, оканчивающиеся на *_internal или использующие суффиксное соглашение (например, dir для хоста, dir_internal для контейнера), относятся к путям: host-сторона работает на вашей машине, internal-сторона — точка монтирования в контейнере. То же различие применяется к портам и именам хостов: ports.http задаёт имя порта контейнера и хостовый порт, на котором он должен появиться; hosts.main — это имя, под которым контейнер резолвится.

ПолеТипОбязательноДопустимо дляОписание
typestringдаapp / tool / infraДискриминатор — выбирает список разрешённых полей для записи.
containerstringнет (по умолчанию имя папки)всеИмя compose-сервиса (по умолчанию имя папки). Команды для одного сервиса (logs, stop, restart, reset --service) находят запущенный контейнер по меткам compose (проект + сервис), поэтому пинить container_name в compose-файле под это имя не нужно — дефолтное именование compose <project>-<service>-<index> работает как есть.
requiredboolнетвсеЕсли true, сервис всегда включён; оверлей не может его выключить.
composelistнетвсеДополнительные файлы оверлея compose, активируемые при включении сервиса.
portsmap[string]int | map[string]{port,scheme}нетвсеИменованные порты контейнера. Краткая форма (int) или развёрнутая {port, scheme}. См. Поле ports.
hostsmap[string]stringнетвсеИменованные имена хостов. См. Поле hosts.
iconstringнетвсеВизуальный индикатор — эмодзи или символ, используемый в дашборде dwe info. Если опущен, используется значение по умолчанию для типа: type: app → 📦, type: tool → 🔧, type: infra → 🧱. См. Поле icon.
infoblockнетвсеМетаданные отображения для дашборда info — переопределение заголовка, выбор ключа host/port и подпути. См. Блок info.
depends_onlistнетapp / infraУпорядоченная зависимость от других сервисов (влияет на порядок deploy). Цель type: tool отклоняется при загрузке.
statuslistнетвсеПользовательские колонки для таблицы dwe status apps / tools / infra по типу — см. Блок status.
on_enableblockнетapp / tool / infraХуки жизненного цикла при включении сервиса. См. Примеры — жизненный цикл переключения.
on_disableblockнетapp / tool / infraХуки жизненного цикла при выключении сервиса.
notesblockнетapp / tool / infraЧеловекочитаемые подсказки, отображаемые в плане команд services enable/disable.
dirstringда (без extends)appПуть к hub-каталогу сервиса на хосте.
dir_internalstringнетappТочка монтирования hub в контейнере.
work_dir_internalstringнетappРабочий каталог по умолчанию для exec/run внутри контейнера.
extendsstringнетappНаследует поля из другой записи type: app. Межтиповой extends отклоняется. См. Наследование.
configslistнетapp⚠️ Устарело — механизм копирования; мигрируйте на render.config. См. Поле configs.
dirslistнетappДополнительные каталоги относительно hub — см. Поле dirs.
cliblockнетappЗначения по умолчанию для dwe shell — см. Блок cli.
renderblockнетappВложенная политика рендера шаблонов (ide / ai / git / config) — см. Блок render.
generatedblockнетappPer-service значения, генерируемые сервисом, которые DWE собирает и переигрывает — см. Блок generated.
bridgeblockнетвсе (по умолчанию выкл. — строго opt-in)Opt-in host-моста — монтирует shim dwe в контейнер сервиса, чтобы dwe работал изнутри него. См. Блок bridge.

ports: всегда карта от имени порта к порту контейнера. Сервисам с одним портом требуется выбранное имя (рекомендация: http для веб, tcp для сырого TCP, ролевые имена вроде mysql / amqp для infra). Значения портов определяются в workspace/services/<name>/service.yml; оверлеи workspace/local.yml могут переназначать отдельные записи — см. поведение глубокого слияния в Поведение загрузки.

ports: — декларативные метаданные: сам по себе этот блок ничего не пробрасывает. Его читают dwe status, dwe info и preflight ports_free, но реальный биндинг живёт в вашем compose-файле, который получает значение через парное правило exports.env (from: services.<name>.ports.http) в workspace/defaults.yml. Порт, объявленный здесь без такого правила, существует только для отображения; dwe validate предупреждает о нём. См. exports.env.

Каждая запись порта принимает две равноценные формы:

  • Краткая (int) — номер порта. Применяется схема уровня сервиса (из info.scheme с фолбэком на runtime.use_https).
  • Развёрнутая (mapping){port: <int>, scheme: "http" | "https"}. Используйте, когда один сервис говорит по разным схемам на разных портах (например, API отдаёт HTTP на 3000 и HTTPS-админку на 9443).
workspace/services/rabbitmq/service.yml
type: infra
container: rabbitmq
ports:
amqp: 5672 # краткая форма — без переопределения схемы
admin: 15672
# workspace/services/api/service.yml — смешанные схемы внутри одного сервиса
type: app
container: api
ports:
http: 3000 # краткая: схема наследуется от info.scheme / runtime
admin: # развёрнутая: этот порт всегда https, независимо от глобального флага
port: 9443
scheme: https

Значения ограничены 1..65535 во время загрузки. Скалярные формы (ports: 80, ports: "80") отклоняются с ErrServicePortsShape. В развёрнутой форме допустимы только поля port и scheme; scheme должна быть "http" или "https" (или опущена).

Приоритет в оверлеях. Запись в workspace/local.yml может использовать любую из форм. Оверлеи мерджатся пополю с унаследованной спецификацией:

  • чистый int (http: 6000) — перекрывает только номер порта; scheme из service.yml сохраняется.
  • mapping (http: {port: 6000}) — то же, что и чистый int (затрагивает только port), полезно ради единообразия с rich-формой.
  • mapping только со scheme (http: {scheme: https}) — перекрывает только схему; унаследованный номер порта сохраняется. В rich-форме оверлея должно присутствовать хотя бы одно из полей port / scheme.
  • mapping с обоими полями (http: {port: 6000, scheme: https}) — перекрывает оба.

scheme: null трактуется как «нет переопределения» (эквивалентно отсутствию ключа).

Эффективная схема. Когда dwe рендерит URL для записи порта, он выбирает схему по цепочке приоритета:

  1. per-port scheme: (развёрнутая форма на этой записи);
  2. схема уровня сервиса info.scheme;
  3. глобальная runtime.use_https (truehttps, falsehttp).

Это также доступно шаблонам через метод ServiceConfig.EffectiveScheme — см. Шаблоны. Для dot-path-доступа (from: / ${...}) per-port схемы доступны под соседним ключом services.<n>.port_schemes.<port-name> (string), который присутствует только у сервисов, фактически использующих переопределение.

URL’ы через reverse-proxy (port_via). Разрешение схемы для проксированного URL маршрутизируемого сервиса идёт по отдельной цепочке, которая намеренно пропускает info.scheme самого прокси (иначе схема прокси «протекла» бы на каждый маршрутизируемый сервис). Цепочка такая:

  1. info.scheme маршрутизируемого сервиса — задаёт и схему URL, и то, какой listener прокси (http или https) будет использован;
  2. per-port scheme: на записи listener’а прокси;
  3. глобальная runtime.use_https.

Это позволяет одному общему прокси обслуживать стек со смешанными схемами. Объявите ports.http: 80 и ports.https: 443 на прокси, затем выставьте info.scheme: https на тех маршрутизируемых сервисах, для которых прокси терминирует TLS; «соседи» без переопределения останутся на http. info.scheme у самого прокси по-прежнему влияет только на его собственную строку в dwe info и не передаётся приложениям, маршрутизируемым через него.

hosts: всегда карта от имени хоста к имени хоста. Симметрично с ports. Для одного имени хоста принято использовать web.

workspace/services/main/service.yml
type: app
hosts:
web: app.localhost

Значения хостов определяются в workspace/services/<name>/service.yml; оверлеи workspace/local.yml могут переназначать отдельные записи — см. поведение глубокого слияния в Поведение загрузки.

Необязательный эмодзи или символ Unicode, отображаемый рядом с именем сервиса в дашборде dwe info при рендеринге блоков auto-urls.

workspace/services/main/service.yml
type: app
icon: "📦"

Если опущен, используется значение по умолчанию на основе типа:

typeИконка по умолчанию
app📦
tool🔧
infra🧱

Иконки рассматриваются как непрозрачный пользовательский контент — эмодзи, соединённые ZWJ (глифы семей, модификаторы профессий, вариации цвета кожи), поддерживаются, но длина не валидируется. Иконка появляется только в выводе dwe info; в других местах не используется.

⚠️ Избегайте эмодзи с Emoji_Presentation=No. Кодовые точки вроде 🛢 (U+1F6E2), 🗄 (U+1F5C4) и (U+2699) являются «text-default» — они рендерятся как цветной эмодзи только за VS-16 (U+FE0F), и многие комбинации терминалов и шрифтов на macOS и Linux игнорируют этот хинт и отрисовывают их в 1 ячейке вместо 2. Lipgloss измеряет их как 2 ячейки, поэтому таблицы status / info недозаполняются и каждая колонка справа от иконки сдвигается.

dwe validate помечает такие иконки (warning, область config.icons) и предлагает безопасные замены из подобранной карты. Во время рендера runtime полностью отбрасывает неоднозначные иконки, а не позволяет им сломать выравнивание колонок — они просто не появятся в дашборде, таблице status или меню переключения. Та же оговорка применяется к иконкам в info.paths[].icon и пользовательским иконкам auto-hosts / auto-urls в workspace/info.yml.

Предпочитайте кодовые точки, являющиеся эмодзи по умолчанию — например, 📦, 🧱, 🐳, 📚, 💾, 🔧, 🧰 — или придерживайтесь однотиповых ASCII / символов псевдографики.

Необязательные метаданные для рендеринга этого сервиса в дашборде dwe info.

workspace/services/main/service.yml
type: app
info:
title: "Main Application"
primary_host: web
primary_port: http
paths:
- name: "API Documentation"
path: /api/docs
icon: "📖"
- name: "Profiler"
path: /?SPX_KEY=dev
icon: ""
ПолеТипПо умолчаниюОписание
titlestringtitle-case(имя-папки)Отображаемое имя сервиса в дашборде (например, "Main Application"). Заменяет значение по умолчанию, выведенное из имени папки.
primary_hoststringwebКакой ключ из hosts показать в основной строке URL (например, console для сервиса с несколькими хостами).
primary_portstringhttpКакой ключ из ports показать в основной строке URL (например, console для сервиса с несколькими портами).
schemestringПереопределение схемы URL уровня сервиса ("http" или "https"). Побеждает глобальную runtime.use_https, уступает per-port scheme: в развёрнутой форме ports.
pathslistУпорядоченный список подпутей под основным URL. См. Записи info.paths ниже.

Когда использовать info.scheme. Установите, если сервис говорит по одной фиксированной схеме, отличной от дефолта проекта — например, dev-сервер Vite с @vitejs/plugin-basic-ssl слушает https://localhost:5173, а остальная часть проекта остаётся на http:// (OAuth-колбэки, обычный dev-бэкенд). Пустой info.scheme означает фолбэк на runtime.use_https, что корректно для проектов с единообразной HTTP- или HTTPS-конфигурацией.

Каждая запись в списке paths объявляет именованный подпуть относительно основного URL сервиса.

paths:
- name: "API Documentation"
path: /api/docs
icon: "📖"
- name: "Profiler"
path: /?SPX_KEY=dev
ПолеТипОбязательноОписание
namestringдаОтображаемое имя пути (например, "API Documentation"). Должно быть непустым и уникальным в списке paths сервиса.
pathstringдаПуть URL относительно основного хоста сервиса (должен начинаться с /). Пример: /api/docs, /admin, /?SPX_KEY=dev.
iconstringнетНеобязательный эмодзи или символ, добавляемый перед именем пути. По умолчанию 🔗, если опущен.

Сервисы без блока info всё равно включаются в блоки дашборда auto-urls (если их типы include совпадают) и рендерят свой основной URL; они просто не добавляют пользовательский заголовок или подпути.

⚠️ Устарело. Механизм копирования configs: (и подполе mountpoint) вытеснен render.config + блоком generated. Он продолжает работать, но dwe validate выдаёт предупреждение, и при каждом шаге копирования один раз срабатывает runtime-уведомление об устаревании. Новым проектам следует рендерить конфиги из шаблонных паков; см. render config для миграции.

Перечисляет конфигурационные файлы, копируемые в hub сервиса при deploy.

configs:
- .env # сокращение: копирует src в configs/.env, монтирует по умолчанию
- file: .env
mountpoint: src/.env # явное место назначения внутри контейнера
ПолеОписание
file (или сокращение-строка)Имя исходного файла (относительно configs/services/<service>/)
mountpoint⚠️ Устарело. Путь относительно dir сервиса (например, src/.env), где файл «трогается» после копирования. Используется билтином service_configs_copy для создания заглушки под вложенные файловые bind-монтирования Docker Desktop virtiofs. Необязательно.

Исходный каталог configs/services/<service>/ принадлежит проекту и коммитится в git; целевой services/<service>/configs/ создаётся при deploy и попадает в gitignore.

Дополнительные каталоги для создания внутри hub-каталога сервиса помимо обязательного src.

dirs:
- logs
- home
- runtime
  • Пути относительны dir сервиса (например, ./services/main/logs).
  • Каталог src/ всегда создаётся и здесь не перечисляется; он также защищён (skip-семантика) в режиме recreate, поэтому исходный код никогда не стирается.
  • Каталог configs/ не обязателен — он создаётся лениво билтином service_configs_copy, когда объявлен блок configs:. Если он нужен заранее или должен стираться под recreate, перечислите его явно здесь.
  • Когда сервис extends другой, поле dirs потомка дописывается к родительскому (дедуплицировано, сначала родитель).
  • Используется билтином service_dirs_ensure во время deploy.

Управляет тем, как dwe shell и выполнение CLI ведут себя для этого сервиса.

cli:
mode: auto # auto | exec | run
shell: bash
user: www-data
workdir: /workspace/src
env:
- XDEBUG_CONFIG="cli_color=1"
ПолеПо умолчаниюОписание
modeautoauto = exec при работающем контейнере, run при отсутствии, ошибка при остановленном; exec = всегда docker exec (ошибка, если не запущен); run = всегда docker compose run --rm
shellbashБинарь shell для вызова внутри контейнера
userтекущий UIDПользователь для запуска внутри контейнера
workdirwork_dir_internal (затем dir_internal)Рабочий каталог shell-сессии
envДополнительные env-переменные, внедряемые в shell-сессию

Флаги CLI переопределяют конфиг cli. Порядок приоритета (от высшего к низшему): флаги --root/--user/--shell/--env → конфиг cli → встроенные значения по умолчанию.

cli.env принимает либо YAML-карту, либо список вида KEY=VALUE; обе формы дают одну и ту же внутреннюю карту и взаимозаменяемы.

# Форма карты
cli:
env:
XDEBUG_CONFIG: "cli_color=1"
PHP_IDE_CONFIG: serverName=dwe
# Форма списка
cli:
env:
- XDEBUG_CONFIG="cli_color=1"
- PHP_IDE_CONFIG=serverName=dwe

Форма списка удобна при копировании из файла .env; форма карты дружелюбнее для наследования и переопределения отдельных ключей через extends:.

Необязательный список пользовательских колонок, дописываемых к таблице status, специфичной для типа — dwe status apps для type: app, dwe status tools для type: tool, dwe status infra для type: infra (и составной dwe status по умолчанию). Каждая запись объявляет имя колонки и герметичный Go-шаблон, вычисляемый построчно против смерженного конфига.

workspace/services/main/service.yml
type: app
container: app-main
dir: ./services/main
status:
- name: CONTAINER
value: "{{ .ServiceCfg.Container }}"
- name: TAG
value: "{{ .Globals.baseImageTag }}"
ПолеТипОбязательноОписание
namestringдаЗаголовок колонки (приводится к верхнему регистру в отрендеренной таблице).
valuestringдаGo-шаблон, вычисляемый через tpl.Render. Герметичный — без доступа к env / FS / сети.

Контракт данных шаблона (Go-шаблоны чувствительны к регистру):

ПутьИсточникРегистр
.ServiceCfg.<Field>типизированный ServiceConfig для сервиса этой строкиPascalCase имена полей Go (.ServiceCfg.Container, .ServiceCfg.Dir)
.Globals.<key>cfg.Raw["globals"], если есть, иначе nilстрочные YAML-ключи (.Globals.baseImageTag)
.Raw.<key>...полная карта cfg.Rawстрочные YAML-ключи (.Raw.services.main.ports.http, .Raw.project.name)

В корне данных только эти три ключа — нет алиасов .Project / .Runtime в корне. Углубляйтесь в .Raw.project.*, .Raw.runtime.*, .Raw.services.*.

Обработка ошибок: шаблон, вызвавший ошибку, рендерится в таблице как и вносит вклад в одно агрегированное предупреждение (warning: N custom status expression(s) failed to render) в stderr. Команда всё равно завершается с кодом 0.

Порядок колонок: в пределах одного раздела status по типу (apps / tools / infra), когда несколько сервисов объявляют пересекающиеся колонки, порядок колонок в отрендеренной таблице — «первое появление» при детерминированной алфавитной итерации по сервисам этого типа. Сервисы, объявляющие меньше колонок, оставляют отсутствующие ячейки как .

Вложенный блок, управляющий тем, рендерит ли (и как именно) система файлы для этого сервиса из шаблонных паков. Содержит четыре подблока: ide, ai, git (у каждого одинаковая структура enabled / template) и config (один пин template — см. Блок render.config).

Управляет тем, рендерит ли (и как именно) IDE-рендер конфигурационные файлы для этого сервиса из шаблонных паков.

render:
ide:
enabled: true # opt-in для IDE-рендера для этого сервиса
template: main-debug # использовать пользовательский шаблонный пак
ПолеПо умолчаниюОписание
enabledtrue для type: app; false иначеВключить этот сервис в IDE-рендер. dwe render ide уважает эту настройку; см. Активация ниже.
templateНеобязательное имя каталога пользовательского шаблонного пака. Должно быть единственным ключом каталога под workspace/templates/ide/ (без разделителей пути, без .., без абсолютных путей, без ведущей .). Если опущен, рендер откатывается к пакам, специфичным для имени сервиса, затем к глобальным. Явные паки строгие: опечатка приведёт к ошибке, а не к молчаливому фоллбэку.

IDE-рендер требует одновременно активации и условий политики:

  1. Активация проекта: Сервис должен быть включён на уровне проекта (через трёхслойное слияние конфига; обязательные сервисы всегда включены).
  2. Политика IDE: Настройка render.ide.enabled должна быть true.

Сервис рендерится только если оба условия выполнены. Отключение любого подавляет рендер.

Политика по умолчанию: сервисы type: app по умолчанию имеют render.ide.enabled: true (opt-out); tool / infra вообще не могут нести блок render: (разрешённые поля по типу). Чтобы рендерить файлы IDE для не-app сервиса, его нужно переопределить по типу — нет per-pack обходного пути.

dwe render ide ищет шаблонные паки в таком порядке; используется первое совпадение:

  1. workspace/templates/ide/<template>/ (если template задан) — строго: пак должен существовать, иначе рендер падает
  2. workspace/templates/ide/<service-name>/ (если template не задан)
  3. workspace/templates/ide/default/ (финальный фоллбэк)

Когда указан явный template:, а пак не найден, рендер падает с ошибкой (ловит опечатки). Когда явный шаблон не задан и неявная цепочка исчерпывается без нахождения пака, рендер пропускается с предупреждением.

После выбора пака команда читает manifest.yml пака, чтобы определить, какие файлы рендерить и какие симлинки создавать. Манифест объявляет:

  • render: исходные файлы шаблонов (должны оканчиваться на .tmpl) и их пути назначения
  • symlinks: относительные симлинки для создания внутри каталога сервиса (необязательно)

Все назначения относительны каталога сервиса (например, services/main/). Вложенные пути допустимы (например, .devcontainer/devcontainer.json).

Эта основанная на манифесте модель позволяет добавлять поддержку любого IDE или инструмента (.cursor/, .zed/, .envrc и т.д.) без модификации кода — добавьте файлы шаблонов и объявите их в manifest.yml.

Когда несколько сервисов разделяют один dir (например, main и main-debug, оба указывающие на ./services/main), только самый дочерний (глубже всего в цепочке extends) сервис рендерит файлы IDE. Остальные отчитываются как пропущенные с предупреждением о коллизии.

Явная позиционная форма dwe render ide <service> трактует аргумент как якорь hub: он валидируется как реальный сервис, но затем разрешается через ту же политику коллизий. Поэтому dwe render ide main на самом деле рендерит main-debug, когда main-debug включён — полезно из per-service пайплайнов deploy, передающих каноническое имя сервиса и ожидающих результат с учётом варианта.

workspace/services/main/service.yml
type: app
dir: ./services/main
# render.ide.enabled по умолчанию true
workspace/services/main-debug/service.yml
type: app
extends: main # тот же dir, что у родителя
dir: ./services/main
render:
ide:
template: main-debug # использовать другой шаблонный пак
# файлы IDE идут в ./services/main/ с содержимым из пака main-debug
# (main-debug побеждает, потому что extends main)

В этом примере dwe render ide создаёт файлы в ./services/main/ с использованием шаблонного пака main-debug и выдаёт предупреждение, что main пропущен из-за коллизии.

Разбор примера: раскладка шаблонного пака

Заголовок раздела «Разбор примера: раскладка шаблонного пака»

Этот пример показывает, как организованы шаблонные паки и какие файлы получаются в каталоге сервиса.

Структура проекта:

workspace/services/
main/
service.yml
main-debug/
service.yml
workspace/templates/ide/
default/
manifest.yml
.devcontainer/devcontainer.json.tmpl
.vscode/settings.json.tmpl
main-debug/
manifest.yml
.devcontainer/devcontainer.json.tmpl
.vscode/settings.json.tmpl
.vscode/launch.json.tmpl

Определения сервисов:

workspace/services/main/service.yml
type: app
dir: ./services/main
# render.ide.enabled по умолчанию true; рендерит, используя пак default
workspace/services/main-debug/service.yml
type: app
extends: main
container: app-main-debug
dir: ./services/main
render:
ide:
template: main-debug # переопределить на использование пака main-debug

После dwe render ide:

services/main/
.devcontainer/
devcontainer.json ← отрендерено из main-debug/.devcontainer/devcontainer.json.tmpl
.vscode/
settings.json ← отрендерено из main-debug/.vscode/settings.json.tmpl
launch.json ← отрендерено из main-debug/.vscode/launch.json.tmpl

Обратите внимание, что main пропущен из-за коллизии (тот же dir, что и у main-debug), поэтому рендерится только шаблонный пак main-debug.

Управляет тем, рендерит ли (и как именно) агентная документация hub-уровень документации для этого сервиса из шаблонных паков.

render:
ai:
enabled: true # opt-in для рендера агент-документации для этого сервиса
template: custom-docs # использовать пользовательский шаблонный пак
ПолеПо умолчаниюОписание
enabledtrue для type: app; false иначеВключить этот сервис в вывод dwe render ai. Когда true, в hub сервиса генерируется агент-ориентированная документация.
templateНеобязательное имя каталога пользовательского шаблонного пака. Должно быть единственным ключом каталога под workspace/templates/ai/ (без разделителей пути, без .., без абсолютных путей, без ведущей .). Если опущен, рендер откатывается к пакам, специфичным для имени сервиса, затем к default. Явные паки строгие: опечатка приведёт к ошибке, а не к молчаливому фоллбэку.

Рендер агент-документации требует одновременно активации и условий политики:

  1. Активация проекта: Сервис должен быть включён на уровне проекта (через трёхслойное слияние конфига; обязательные сервисы всегда включены).
  2. Политика агент-документации: Настройка render.ai.enabled должна быть true.

Сервис рендерится только если оба условия выполнены. Отключение любого подавляет рендер.

Политика по умолчанию: сервисы type: app по умолчанию имеют render.ai.enabled: true (opt-out); не-app сервисы по умолчанию имеют false и должны подключаться явно (установите render.ai.enabled: true). Установите render.ai.enabled: false, чтобы подавить генерацию агент-документации для app-сервиса.

dwe render ai ищет шаблонные паки в таком порядке; используется первое совпадение:

  1. workspace/templates/ai/<template>/ (если template задан) — строго: пак должен существовать, иначе рендер падает
  2. workspace/templates/ai/<service-name>/ (если template не задан)
  3. workspace/templates/ai/default/ (финальный фоллбэк)

Когда указан явный template:, а пак не найден, рендер падает с ошибкой (ловит опечатки). Когда явный шаблон не задан и неявная цепочка исчерпывается без нахождения пака, рендер пропускается с предупреждением.

После выбора пака команда читает manifest.yml пака, чтобы определить, какие файлы рендерить и какие симлинки создавать. Манифест объявляет:

  • render: исходные файлы шаблонов (должны оканчиваться на .tmpl) и их пути назначения
  • symlinks: относительные симлинки для создания внутри hub сервиса (должны ссылаться на выводы из render)

Все назначения относительны hub-каталога сервиса (например, services/main/). Вложенные пути допустимы (например, .claude/CLAUDE.md).

Когда несколько сервисов разделяют один dir (например, main и main-debug, оба указывающие на ./services/main), только канонический владелец hub — наименее дочерний сервис (мельче всего в цепочке extends) — рендерит агент-документацию. Обоснование: агент-документация описывает идентичность hub, и когда потомок extends родителя и разделяет его dir, родитель владеет hub; потомок — runtime-вариант того же воркспейса. Проигравшие варианты отчитываются как пропущенные с предупреждением о коллизии.

Явная позиционная форма dwe render ai <service> трактует аргумент как якорь hub (так же, как render ide): аргумент валидируется как реальный сервис, затем разрешается через политику коллизий. Поэтому dwe render ai main-debug всё равно рендерит main, когда оба включены — вариант разрешается к каноническому владельцу hub.

(Примечание: это отличается от dwe render ide, где побеждает самая глубокая цепочка extends, потому что конфиги IDE — про per-variant переопределения.)

Разбор примера: раскладка шаблонного пака

Заголовок раздела «Разбор примера: раскладка шаблонного пака»

Этот пример показывает, как организованы шаблонные паки агента и какие файлы получаются в каталоге сервиса.

Структура проекта:

workspace/services/
main/
service.yml
workspace/templates/ai/
default/
manifest.yml
AGENTS.md.tmpl
.claude/CLAUDE.md.tmpl

Манифест (workspace/templates/ai/default/manifest.yml):

render:
- from: AGENTS.md.tmpl
to: AGENTS.md
- from: .claude/CLAUDE.md.tmpl
to: .claude/CLAUDE.md
symlinks:
- link: CLAUDE.md
to: AGENTS.md

Пример шаблона (AGENTS.md.tmpl):

# {{.Service}} Service Hub
This is the {{.Service}} service running inside a DWE-managed hub.
The application source code is at `src/`.
Service container: {{.ServiceCfg.Container}}
Workspace root: {{.ServiceCfg.DirInternal}}

Определение сервиса (workspace/services/main/service.yml):

type: app
dir: ./services/main
# render.ai.enabled по умолчанию true; рендерит, используя пак default

После dwe render ai:

services/main/
AGENTS.md ← отрендерено из AGENTS.md.tmpl
CLAUDE.md ← симлинк на AGENTS.md
.claude/
CLAUDE.md ← отрендерено из .claude/CLAUDE.md.tmpl

Управляет тем, рендерятся ли (и как именно) shell git-хуки в каталог src/.git/hooks/ сервиса из шаблонных паков.

render:
git:
enabled: true # opt-in для рендера git-хуков для этого сервиса
template: custom-hooks # использовать пользовательский шаблонный пак
ПолеПо умолчаниюОписание
enabledtrue для type: app; false иначеВключить этот сервис в вывод dwe render git. Отражает политику по умолчанию render.ide.
templateНеобязательное имя каталога пользовательского шаблонного пака. Должно быть единственным ключом каталога под workspace/templates/git/ (без разделителей пути, без .., без абсолютных путей, без ведущей .). Если опущен, рендер откатывается к пакам, специфичным для имени сервиса, затем к default. Явные паки строгие: опечатка приведёт к ошибке, а не к молчаливому фоллбэку.

Наследование extends для render.git.enabled и render.git.template следует тем же правилам, что render.ide и render.ai: явные значения потомка переопределяют родительские; опущенные значения наследуются. Разрешение коллизий на разделяемом dir использует deepest-extends-wins (так же, как render.ide).

Хуки записываются в <svc.Dir>/src/.git/hooks/<basename> с режимом 0755. Сервисы, у которых отсутствует src/.git (нет git-чекаута) или это файл (указатель worktree/submodule), пропускаются с предупреждением. См. render git для полного справочника, схемы манифеста и примеров.

Управляет рендером конфигурационных файлов для этого сервиса — основанный на рендере преемник устаревшего механизма копирования configs:. Конфиги становятся чистыми выводами рендера из шаблонного пака, записываемыми прямо в дерево hub сервиса (обычно src/..., уже примонтированное как dir), переигрывая любые собранные значения generated.

render:
config:
template: laravel # необязательный пин пака; иначе соглашение + .local
ПолеПо умолчаниюОписание
templateНеобязательное имя каталога пользовательского шаблонного пака под workspace/templates/config/<template>/. Когда задан, разрешение строгое (опечатка приводит к ошибке, а не к молчаливому фоллбэку). Когда опущен, разрешение идёт по цепочке имя сервиса → предки по extendsdefault, плюс соседнее переопределение <pack>.local/.

В отличие от render.ide / ai / git, у render.config нет флага enabled — рендер конфигов гейтится исключительно тем, разрешается ли пак (opt-in: нет пака → нет рендера). Шаблоны конфигов используют сокращение ${...} (например, ${services.main.ports.http}, ${vars.databases.main}, ${generated.app_key}) — намеренное расхождение с сырым субстратом {{ }}, используемым другими видами рендера. См. render config для полного справочника, субстрата, схемы манифеста и потока harvest/replay.

Объявляет per-service значения, генерируемые сервисом (Laravel APP_KEY, Magento crypt.key, …), которые DWE собирает обратно из собственного выходного файла сервиса в долговременное хранилище (.dwe/generated.yml) и переигрывает при каждом последующем рендере через пространство имён ${generated.<name>}. Модель — harvest, а не mint: значение генерирует сам сервис (например, php artisan key:generate); DWE лишь считывает его обратно как строку.

workspace/services/main/service.yml
type: app
dir: ./services/main
generated:
app_key:
file: src/.env # выходной файл, относительно hub сервиса (svc.Dir)
pattern: '^APP_KEY=(.*)$' # regex; группа захвата 1 = значение
ПолеТипОбязательноОписание
filestringдаВыходной файл, в который сервис записывает значение, относительно hub-каталога сервиса (svc.Dir). Должен быть содержащимся относительным путём (без ..).
patternstringдаRegex, применяемый построчно; группа захвата 1 — собираемое значение. Должен компилироваться и объявлять ≥1 группу захвата.

Ключ карты (app_key) — это идентификатор ${generated.<name>}. dwe validate отклоняет невалидный regex, отсутствие группы захвата, выходящий за пределы путь file или имя поля, которое не является валидным идентификатором ${generated.<name>}.

Шаг генерации сервиса обычно гейтится предикатом generated-missing <svc> <field>, так что он выполняется только при первом deploy (когда значение ещё не собрано), а затем собирается билтином service_generated_harvest. Значение переживает run / передеплой и сохраняется при reset, если не передан --clear-generated. См. render config для полного потока deploy, схемы хранилища и бутстрапа уже закоммиченного секрета через dwe render config <svc> --harvest.

Подключает сервис к host-мосту — DWE монтирует небольшой статический shim dwe в контейнер, чтобы команды dwe (git-хуки, команды проекта, read-only диагностика) выполнялись изнутри контейнера, перенаправляясь на host-демона. Мост выключен по умолчанию для всех типов сервисов — включайте явно через enabled: true.

workspace/services/main/service.yml
type: app
dir: ./services/main
bridge:
enabled: true # по умолчанию: false — мост строго opt-in
# shim_path: /usr/local/bin/dwe # переопределение точки монтирования (коллизия base-образа)
# on_unreachable: fail # fail | warn — политика shim, когда демон недоступен
ПолеТипОбязательноПо умолчаниюОписание
enabledbool (tristate)нетfalse (для всех типов)Внедрить shim-бинарник и bridge-монтирования в контейнер этого сервиса.
shim_pathstringнет/usr/local/bin/dweАбсолютный путь в контейнере, куда монтируется shim; переопределите, если base-образ уже содержит файл там.
on_unreachablestringнетfailfail — shim печатает ошибку и выходит с кодом 1, когда host-демон недоступен (хук блокирует коммит); warn — печатает предупреждение и выходит с кодом 0.

bridge.enabled — это tristate, наследуемый через extends: сервиса так же, как render.git.enabled: явное значение в потомке выигрывает, неустановленный потомок наследует родителя, а выключенное значение по умолчанию применяется, только если ни один из них не задан. shim_path и on_unreachable наследуются, когда потомок оставляет их пустыми. Сервис type: app с включённым мостом должен объявлять пару dir / dir_internal — поверх неё работает трансляция рабочей директории shim, и dwe validate (домен bridge) предупреждает, когда её нет. См. Host-мост для транспортов, in-container политики команд, генерируемого compose-оверлея и жизненного цикла демона.