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

workspace.yml / defaults.yml / local.yml

Три слоя смерженного конфига DWE.

flowchart TB
  L1["1 · workspace.yml<br/>tracked · структурный скелет"]
  L2["2 · workspace/defaults.yml<br/>tracked · версионированные дефолты"]
  L3["3 · workspace/local.yml<br/>gitignored · переопределения на пользователя"]
  R[(Эффективный DweConfig<br/>+ DweConfig.Raw)]

  L1 -- "merged into" --> L2
  L2 -- "overridden by<br/>(local wins)" --> L3
  L3 -- "deepMerge result" --> R

  R --> ENV[dwe render env → .env]
  R --> DASH[dwe info]
  R --> RES[ResolvePath dot-paths<br/>exports, docker.yml,<br/>commands, info templates]

Читайте сверху вниз: каждая стрелка — это «следующий слой накладывается сверху». local.yml идёт последним, поэтому любой ключ, который он задаёт, перекрывает тот же ключ из defaults.yml или workspace.yml. Ключи, отсутствующие в local.yml, проваливаются к defaults.yml, затем к workspace.yml, и в конце — к Go-zero value.

Три файла используют одно пространство имён — один и тот же ключ в разных слоях — это одна и та же настройка. Слой 1 задаёт структуру, слой 2 заполняет значения по умолчанию, слой 3 переопределяет для локальной машины. Ни один из трёх не обязан объявлять каждый ключ; отсутствующие ключи просто проваливаются к слою, где они заданы, и в конце — к Go-zero value.

workspace/local.yml опционален: если он отсутствует, мердж молча пропускает слой 3.

НазначениеСлой
Имя и префикс проектаworkspace.yml
Порты / хосты сервисов (apps, tools, infra)workspace/services/<name>/service.yml (per-entry карты ports: / hosts:)
Структурные определения сервисов (container / compose / status / render)workspace/services/<name>/service.yml
Опциональное состояние enabled для сервисов (для всех типов)defaults.yml (переопределяемо в local.yml)
Правила экспорта (exports.env)defaults.yml
Дефолты блока vars.db.*defaults.yml
Активное состояниеlocal.yml
Значения портов / хостов сервисовworkspace/services/<name>/service.yml (проектные определения) и local.yml (переопределения на разработчика, deep-merge по имени записи)
Личные креды (vars.db.user, vars.db.password)local.yml
Включение debug / опциональных сервисовlocal.yml
Конфигурация, сгенерированная мастеромlocal.yml (пишется dwe deploy при ответе на вопросы setup или конфликты портов)

Сами определения сервисов (apps, tools, infra — включая их порты / хосты) лежат в per-folder файлах workspace/services/<name>/service.yml, которые загружаются отдельно и не участвуют в этом мердже. Трёхслойный оверлей содержит services.<name>.enabled, services.<name>.ports и services.<name>.hosts. Карты портов и хостов глубоко мержатся по имени записи, поэтому частичное переопределение затрагивает только перечисленные ключи.

Команда dwe deploy включает интерактивный мастер, запускающийся на свежих проектах (когда workspace/local.yml отсутствует или пуст). Мастер собирает ответы на вопросы, объявленные в workspace/setup.yml, и предлагает переопределить порты при наличии конфликтов. Все ответы глубоко мержатся в local.yml и атомарно записываются до того, как деплой продолжится. Подробности схемы см. в workspace/setup.yml.

CLI хранит смерженный результат в двух местах: в типизированной структуре DweConfig (с полями вроде DweConfig.Services и DweConfig.Runtime.UseHTTPS) и в обычной мапе DweConfig.Raw. Мапа Raw используется для разрешения dot-path.

Dot-path — это цепочка ключей через ., проходящая по смерженной YAML-мапе. Примеры:

  • services.main.ports.http80
  • services.adminer.enabledfalse
  • services.main.container"app-main"
  • services.main.hosts.web"app.localhost"
  • vars.db.user"root" (свободные значения живут под vars:)

Dot-path’ы используются:

  • правилами экспорта в defaults.yml (from:, when:)
  • шаблонными выражениями ${...} в docker.yml (project_name)
  • шаблонными выражениями ${...} в декларативных командах (workspace/commands/)
  • Go-шаблонами {{ ... }} в info.yml (через типизированную структуру, не Raw)

Пути services.<name>.* в смерженной мапе наполняются из каждого workspace/services/<name>/service.yml (канонической декларации сервиса с полем type:). Каждый оверлейный слой валидируется по декларированному набору полей, три слоя сливаются, затем определяется enabled для каждого сервиса (required выигрывает; иначе берётся значение из смерженного оверлея, по умолчанию false). Каждый разрешённый сервис — включая вложенные карты ports / hosts и разрешённые поля вроде container, dir, compose — становится доступен под services.<name> в смерженном конфиге. Поэтому правила экспорта и шаблоны могут использовать services.main.container, services.main.ports.http, services.adminer.hosts.web, services.catalog.enabled и т.д., не зная о внутренней структуре per-service папок.

Корень смерженного трёхслойного конфига строгий. После слияния трёх слоёв DWE проверяет ключи верхнего уровня по фиксированному allowlist’у:

project · runtime · state · exports · compose · ui · docs · services · vars · update · bridge · stop

(schema_version также входит в allowlist как зарезервированные forward-compat метаданные — обычный член списка, не отдельное исключение.) Любой другой ключ верхнего уровня — в любом слое — это жёсткая ошибка при загрузке:

workspace.yml: unknown top-level key "db" — move custom values under "vars:" (e.g. vars.db.*)

Так опечатки в формализованных ключах (runtim:, exprots:) падают громко, а не проглатываются молча, и схему можно ужесточать, не конфликтуя со специфичными для проекта значениями. Та же ошибка выводится как error-диагностика dwe validate.

Произвольные, специфичные для проекта значения живут в единственном типизированном блоке vars:. Корень строгий, но внутри vars: можно всё — любые ключи, любая вложенность, без валидации:

vars:
db:
database: myapp
user: root
password: secret
app:
timeout: 30
retries: 3

vars — обычный смерженный ключ, поэтому его содержимое достижимо через dot-path под префиксом vars.:

  • Правила экспорта: from: vars.db.user
  • Шаблоны ${...}: ${vars.db.database}
  • Пользовательские команды / проверки config_keys_present: vars.db.api_key

vars.* резолвится через DweConfig.Raw по dot-path так же, как services.*.

Команда dwe vars перечисляет, читает, редактирует и трассирует каждое значение под этим блоком — см. vars.md про подкоманды, модель слоёв author/local/effective, запись в local.yml с сохранением комментариев, статическое сканирование использований и allowlist контейнерной записи bridge.vars_writable.

Когда включён хост-бридж, команда dwe vars set становится доступна изнутри dev-контейнера. Чтобы скомпрометированный или неаккуратный контейнер не мог переписать произвольные значения проекта на хосте, блок верхнего уровня bridge.vars_writable — это deny-by-default allowlist путей vars.*, которые контейнерный vars set может изменять. С хоста команда не ограничена; этот шлюз применяется только когда вызов приходит через бридж.

workspace/defaults.yml
bridge:
vars_writable:
- vars.app.timeout # точный путь — записываем только этот лист
- vars.feature_flags.* # wildcard по границе точки — любой лист строго ниже

Сопоставление идёт по границе точки, а не наивным префиксом:

  • Точный шаблон (vars.db.host) совпадает только с этим же путём.
  • Шаблон с хвостовым wildcard’ом (vars.db.*) совпадает с путём строго ниже базы — он разрешает vars.db.host, но запрещает сам vars.db, а также похожие vars.dbx.host и vars.database.host.
  • Пустой или отсутствующий список означает, что ни одна переменная не записываема из контейнера — безопасное значение по умолчанию. Некорректные шаблоны (одинокий *, * в середине) фейлятся закрыто.

bridge.vars_writable значение-мержится по трём слоям и читается nil-безопасно, поэтому ведёт себя как любой другой формализованный ключ верхнего уровня.

Рекомендация: объявляйте его в workspace/defaults.yml. Это общекомандная политика безопасности уровня проекта — она должна быть в git и одинакова у всех, а не быть индивидуальной настройкой. Размещение в gitignore’нутом workspace/local.yml привело бы к молчаливому расхождению поверхности контейнерной записи на разных машинах и не путешествовало бы вместе с репозиторием. local.yml оставьте для индивидуальных значений (порты, креды, флаги enabled).

Этот блок управляет только тем, что контейнеру можно писать; он отличается от пер-сервисного блока services.<name>.bridge, который управляет тем, подключён ли сервис к бриджу вообще. Полная поверхность контейнера — в разделе Хост-бридж → политика команд.

Назначение: идентификация проекта и структурный скелет. Отслеживается git. Редко меняется после первоначальной настройки.

Порядок загрузки: слой 1 (базовый).

Пример:

project:
name: laravel
prefix: myprefix
ПолеТипОписание
project.namestringКороткий идентификатор проекта (используется в именах контейнеров, .env)
project.prefixstringПрефикс для имени Docker Compose-проекта и меток контейнеров

project.prefix и project.name комбинируются, образуя имя Docker Compose-проекта через шаблон в docker.yml (${project.prefix}-${project.name}).

Опциональный верхнеуровневый блок update: управляет пробой самообновления, которую dwe run выполняет на git-репозитории корня проекта перед выполнением любой lifecycle-фазы. Это формализованный блок, участвующий в трёхслойном мердже (скаляр mode — last-layer-wins), поэтому автор проекта может задать политику в workspace.yml, а разработчик — переопределить её в local.yml.

update:
mode: on # on | off
ПолеТипПо умолчаниюОписание
update.modestringoff, когда блок отсутствует; on, когда блок присутствует, но mode не задан или пустОдно из on, off. Само написание ключа update: — это opt-in.

Поведение mode:

ModeФетчитПуллитПоведение, когда отстаёт
onдас согласияИнтерактивный TTY: спрашивает перед git pull --ff-only. Non-TTY / CI: предупреждает «отстаёт, пропускаю» и продолжает.
offнетнетПроба выключена (то же, что флаг --no-update).

Семантика разрешения (UpdateConfig.EffectiveMode()): отсутствующий блок (nil) → off; присутствующий блок, у которого mode не задан или пуст → on; иначе буквальный mode. Плохое значение (например, update: { mode: yes }) — жёсткая ошибка при загрузке конфига и error-диагностика dwe validate.

Приоритет в runtime при dwe run: флаг --no-update > флаг --update <mode> > update.mode из смерженного конфига.

Этот блок отделяет включение обновления от lifecycle-фаз. (См. lifecycle.md и интеграцию с git → проба обновления.)

Опциональный верхнеуровневый блок stop: настраивает поведение остановки всего стека. Это формализованный блок, участвующий в трёхслойном мердже, поэтому автор проекта может задать значение по умолчанию в workspace.yml, а разработчик — переопределить его в local.yml.

stop:
port_release_timeout: 60s # Go-длительность ("60s", "2m", "1m30s") или просто секунды ("90"); "0" отключает
ПолеТипПо умолчаниюОписание
stop.port_release_timeoutстрока-длительность60sСколько dwe stop (и стоп-этап dwe restart) ждёт фактического освобождения опубликованных хостовых портов стека после docker compose down. 0 отключает ожидание.

Зачем это нужно. На Docker Desktop / OrbStack (macOS) хостовый форвардер портов освобождает опубликованный порт (например, :80 у caddy) на мгновение позже, чем контейнер исчезает из docker ps — и задержка растёт тем больше, чем дольше работал контейнер. Без ожидания run-этап dwe restart гонится с этим освобождением, и preflight-проверка ports_free ложно сообщает о конфликте с собственным, только что освобождённым портом проекта. Поэтому dwe stop ждёт реального освобождения портов перед возвратом, показывая живой спиннер + таймер с указанием порт(ов), которые ещё заняты.

Ожидается только порт, который занят и не принадлежит ни одному живому контейнеру. Обычно это «зависший» форвард только что снятого контейнера; не-Docker процесс на хосте, держащий порт, на этом уровне неотличим, поэтому он тоже ожидается до таймаута (затем выводится предупреждение). Порт, который всё ещё держит живой (чужой) контейнер, никогда не ожидается, поэтому стоп не может зависнуть на чужом контейнере. На нативном Linux порты освобождаются синхронно, поэтому ожидание завершается сразу. Превышение таймаута лишь печатает предупреждение и продолжает (повторная проба preflight при следующем старте — финальный страховочный механизм) — стоп при этом никогда не падает.

Поднимите значение, если у проекта есть медленно завершающиеся сервисы; задайте 0, чтобы полностью отказаться от ожидания. Некорректное или отрицательное значение (например, 1minute, -5s) — жёсткая ошибка при загрузке конфига; 0 — единственный «выключатель».

Конфигурирует поведение рендеринга документации и кеширования для команд dwe docs.

docs:
mermaid: auto # auto | mmdc | off (default: auto)
cache_size_mb: 100 # cache size in MB (default: 100)

docs.mermaid: контролирует, как рендерятся mermaid-диаграммы в документации.

  • auto (по умолчанию): использовать mmdc (mermaid-cli), если найден в $PATH, иначе показать диаграммы как блоки кода.
  • mmdc: требовать наличие mmdc; при отсутствии выводить error-плейсхолдер, но продолжать.
  • off: никогда не рендерить диаграммы; всегда показывать блоки кода.

docs.cache_size_mb: максимальный размер в MB для кеша mermaid-диаграмм (PNG-файлы, хранящиеся в $XDG_CACHE_HOME/dwe/mermaid/). Кеш использует LRU-вытеснение при превышении лимита. По умолчанию 100 MB. Значение должно быть неотрицательным; ноль приводит к дефолту 100.


Рекомендуемое соглашение о раскладке файлов

Заголовок раздела «Рекомендуемое соглашение о раскладке файлов»

Все три слоя разделяют один строгий набор ключей, поэтому любой блок может появиться в любом слое. Следующее разделение — только соглашение (его нарушение не ошибка), но оно держит слои читаемыми:

СлойСодержитЗачем
workspace.ymlКомпактные формализованные блоки: project, ui, updateМаленькие, структурные, редко меняются
defaults.ymlОбъёмные блоки: vars, exports, оверлей services, runtime, bridge.vars_writableВерсионированные командные дефолты; самый большой контент. bridge.vars_writable — общекомандная политика безопасности, держите её здесь, а не в local.yml (см. замечание про allowlist выше)
local.ymlЛичные переопределения: state, vars.db.password, тогглы сервисов, compose.extra, update.modeНа разработчика, gitignored

Например, автор проекта включает политику обновления в workspace.yml (update: { mode: on }), а разработчик, который хочет локально пропустить пробу, переопределяет её в local.yml (update: { mode: off }).


Назначение: версионированные дефолты для всего проекта. Отслеживается git. Содержит всю runtime-конфигурацию, не относящуюся к структурной идентификации проекта.

Порядок загрузки: слой 2 (мерджится поверх workspace.yml).

Секции:

Переключает опциональные сервисы любого типа (сервисы, объявленные в workspace/services/<name>/service.yml без required: true). Apps, tools и infra используют одно оверлейное пространство имён — дискриминатор type: находится в service.yml каждого сервиса, не здесь.

services:
main-debug: # type: app
enabled: false
catalog: # type: app
enabled: true
adminer: # type: tool
enabled: false
mailpit: # type: tool
enabled: true

Разрешённые поля под services.<name> в любом оверлейном слое — это enabled, ports и hosts. Добавление структурных полей вроде container:, compose:, extends: и т.д. — это ошибка оверлея со слоевой проверкой — такие поля лежат в workspace/services/<name>/service.yml. Карты портов и хостов глубоко мержатся по имени записи. Required-сервисы всегда активны и переключателя не имеют.

Runtime-настройки, влияющие на генерацию .env и info-дашборд, но не относящиеся к конкретному сервису. Порты / хосты на сервис находятся в workspace/services/<name>/service.yml в картах ports: / hosts: каждой записи (и доступны как dot-path’ы services.<name>.ports.<port-name> / services.<name>.hosts.<host-name>).

runtime:
use_https: false
spx:
path: ""
ПолеОписание
runtime.use_httpsИспользуют ли URL’ы HTTPS (экспортируется как USE_HTTPS).
runtime.spx.pathURL-путь профайлера SPX (пусто = выключено).
state: ""

Имя активного состояния. Пустая строка означает отсутствие состояния. Экспортируется как STATE в .env. Переопределяйте в local.yml (например, state: staging).

Декларативные правила экспорта, управляющие генерацией .env. Каждое правило сопоставляет dot-path в смерженном конфиге имени env-переменной. Все поля сервисов — container, enabled, ports.<name>, hosts.<name> — находятся под services.<name>.*.

exports:
env:
- name: APP_PORT
from: services.main.ports.http
format: int
- name: TOOL_ADMINER_ENABLED
from: services.adminer.enabled
format: bool
- name: ADMINER_PORT
from: services.adminer.ports.http
format: int
when: services.adminer.enabled
- name: ADMINER_HOST
from: services.adminer.hosts.web
when: services.adminer.enabled
Поле правилаТипОписание
namestringИмя env-переменной в .env
fromstringDot-path в смерженный конфиг
defaultstringFallback-значение, когда путь отсутствует
requiredboolОшибка, если путь отсутствует и нет дефолта
formatstringstring (по умолчанию), bool, int
whenstringDot-path; правило пропускается, когда значение falsy
commentstringПишется как # comment над переменной

dwe render env всегда выводит три переменные до выполнения любого правила, независимо от exports.env:

ПеременнаяИсточникЗаметки
PROJECTproject.nameИспользуется Docker labels и Make-таргетами
UIDхостовый UIDЖёстко зафиксирован в 1000 на macOS, реальный UID на Linux/WSL — сборки контейнеров остаются детерминированными между хостами
GIDхостовый GIDТа же логика, что для UID

Они управляются CLI; не декларируйте их повторно как правила экспорта.

Конфигурация compose-файлов, используемая Docker control plane.

compose:
base: compose.yaml
ПолеОписание
compose.baseБазовый compose-файл (всегда подключается)
compose.extraЗдесь невалидно. Файлы оверлеев на разработчика принадлежат local.yml. См. Compose-оверлеи.

Оверлеи для конкретных сервисов находятся под services.<name>.compose (список путей к файлам для каждой записи сервиса) в workspace/services/<name>/service.yml. Полный порядок вывода compose-файлов (включая оверлеи на разработчика) описан в разделе Compose-оверлеи в local.yml.


Назначение: переопределения на стороне пользователя. Gitignored, никогда не коммитится. Шаблон — в workspace/local.example.yml.

Порядок загрузки: слой 3 (мерджится последним — наивысший приоритет).

Пример переопределений:

state: staging
services:
main-debug:
enabled: true
redis_insight:
enabled: false
runtime:
use_https: true

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

Если local.yml не существует, слой 3 молча пропускается.

local.yml — единственное место, откуда можно подмешать дополнительные Docker Compose overlay-файлы в цепочку docker compose -f …, которую собирает dwe. Доступны два слоя:

  • На уровне проектаcompose.extra: [<path>, …] добавляется в самый конец списка -f при каждом вызове dwe docker, независимо от того, какие сервисы включены.
  • На уровне сервисаservices.<name>.compose.extra: [<path>, …] добавляется сразу после собственных compose-файлов сервиса (из workspace/services/<name>/service.yml). Сервисный блок переиспользует тот же enabled-gate: оверлеи отключённого сервиса не попадают в активный список -f, но появляются под dwe docker --all.
# workspace/local.yml — gitignored, на разработчика
compose:
extra:
- compose.local.yml # добавляется последним (на уровне проекта)
services:
dev:
compose:
extra:
- compose/dev.local.yml # добавляется после compose-файлов services/dev

Итоговый порядок, собираемый composeFiles():

compose.base
→ tools (alpha-sorted) — каждый: svc.compose… + svc.local-extras…
→ infra (alpha-sorted) — каждый: svc.compose… + svc.local-extras…
→ apps (alpha-sorted) — каждый: svc.compose… + svc.local-extras…
→ compose.extra… (на уровне проекта, всегда последним)

Docker Compose мержит более поздние -f-файлы поверх более ранних — поэтому слой на уровне проекта может переопределить per-service оверлеи. Если это нежелательно, ограничьте переопределение блоком конкретного сервиса.

Правила схемы:

  • Оба слоя принимают только ключ extra: внутри compose: — всё остальное считается жёсткой ошибкой.
  • Пути относительны корню проекта (директории с workspace.yml) и хранятся как написаны; нижестоящий код выставляет cmd.Dir в корень проекта.
  • Абсолютные пути отвергаются (/etc/... / ~/... — обошли бы containment).
  • ..-побеги отвергаются через pathsafe.ContainedRel.
  • Каждый путь должен существовать на момент загрузки конфига; отсутствие файла — жёсткая ошибка с указанием как написанного, так и резолвленного абсолютного пути. Исключение: проверка существования пропускается для отключённых сервисов — разработчик может заранее прописать пути в local.yml до создания файла, а отключённые сервисы всё равно исключаются из ComposeFiles() во время выполнения. Структурные проверки безопасности (отказ от абсолютных путей, containment, симлинки) для оверлеев отключённых сервисов по-прежнему выполняются, поскольку ComposeFilesAll() (используемый dwe docker --all) включает их.
  • Дублирующиеся пути между слоями (или внутри слоя) не дедуплицируются — Docker Compose терпит дубликаты; пусть он сам сообщит о проблеме, если она будет.
  • compose.extra в workspace.yml, defaults.yml или service.yml отвергается с диагностикой, указывающей на workspace/local.yml.

Не путайте с docker.local.yml: docker.local.yml переопределяет политику выполнения compose (имя проекта, аргументы подкоманд, окружение процесса). local.yml → compose.extra добавляет compose-оверлеи сервисов (env, тома, порты в контейнерах). Это независимые поверхности — см. docker.md про файл политики.

Наследование: когда сервис имеет extends: <parent> в своём service.yml, ребёнок наследует services.<parent>.compose.extra из local.yml только если сам ребёнок ничего не объявил. Если у ребёнка есть собственный services.<child>.compose.extra, выигрывает список ребёнка (без мерджа).

Мотивирующий пример — git-идентичность разработчика внутри контейнера dev:

# workspace/compose/dev.local.yml — также gitignored
services:
dev:
environment:
GIT_CONFIG_COUNT: "2"
GIT_CONFIG_KEY_0: user.name
GIT_CONFIG_VALUE_0: Jane Doe
GIT_CONFIG_KEY_1: user.email
GIT_CONFIG_VALUE_1: jane@example.com
workspace/local.yml
services:
dev:
compose:
extra:
- compose/dev.local.yml

Коммиты, сделанные внутри контейнера dev, теперь используют project-specific идентичность разработчика без правки ~/.gitconfig на хосте и без изменения какого-либо git-tracked файла в репозитории.

Добавьте файлы оверлеев в gitignore вместе с самим local.yml — это per-developer артефакты.

  • Редактирование defaults.yml для личных настроек — изменения отслеживаются и влияют на каждого члена команды. Личные переопределения всегда кладутся в local.yml.
  • Коммит local.yml — он gitignored не просто так (может содержать креды).
  • Указание state: в defaults.yml — состояние по своей природе индивидуальное, кладите его в local.yml.
  • Коллизия скаляров — если defaults.yml выставляет state: "", а local.yml выставляет state: staging, эффективное значение — staging. Если local.yml опускает state, выигрывает значение из defaults.yml.
  • Списки заменяют, карты мерджатся — карты deep-merge’атся: повторная декларация services в local.yml переопределяет только перечисленные ключи, остальные проваливаются из defaults.yml. Списки же заменяются целиком: выставление bridge.vars_writable: ["vars.db.*"] в local.yml отбрасывает каждую запись, которую имели нижние слои, поэтому включайте полный нужный список.

workspace.yml может нести опциональный верхнеуровневый блок ui:, конфигурирующий интерактивный браузер команд. См. ui.md для схемы, дефолтов и семантики omit-vs-false для *bool. Поведение не меняется для проектов, опускающих блок.

  • dwe render env --out .env — перегенерировать .env из смерженного конфига
  • dwe render ide / dwe render ai / dwe render git — pack-based рендереры; см. справочник render
  • dwe info — показать дашборд (использует смерженный конфиг + info.yml)
  • dwe status — композитный read-only view (apps + tools + infra + deploy + topology + git + daemons)
  • dwe status apps / dwe status tools / dwe status infra — таблицы по типу
  • dwe compose argv — показать эффективную compose-команду со всеми флагами (полезно для отладки разрешения dot-path в docker.yml)