Starting a new project with dwe init
You want to put a brand-new project under DWE. There is no workspace.yml yet, and hand-assembling the config file by file is tedious and easy to get subtly wrong. dwe init does the bootstrap for you: one command writes a minimal-but-complete project that loads and dwe validates clean on the first run.
This is the mirror image of Joining a DWE project: that guide is for a repo that already ships DWE config; this one creates that config from nothing.
What dwe init is for
Section titled “What dwe init is for”dwe init is the only DWE command that runs outside a project — it creates one rather than acting inside one. It is:
- A skeleton to configure, not a running stack. It writes a minimal but valid project structure —
workspace.yml,workspace/defaults.yml, one enabledappservice, a basecompose.yaml, an AI template pack, a starter test scenario, and a set of commented override files. You fill in the real service config — image or build, ports, hosts — from there. The override files ship fully commented, so the built-in deploy/lifecycle defaults stay in effect until you deliberately take one over. - Safe to re-run. On a directory with no
workspace.ymlit fills gaps and never overwrites an existing file unless you pass--force. If a project already exists there, it refuses to clobber it silently: interactively it asks you to confirm a recreate, and non-interactively it stops unless--forceis passed.
Interactive run
Section titled “Interactive run”In an empty directory, just run:
dwe initA short form asks for:
- Project name (required) — written to
workspace.ymlasproject.name. - Compose prefix (default
dwe) — theproject.prefixthat namespaces your Docker resources. - Branding (optional) — a title, a tagline (the short subtitle under the header), and an accent color (a 6-digit hex code such as
#2EC3EB) rendered intoworkspace/styles.yml. Each field is validated as you type. Leave these blank for a generic header; you can always brand it later.
The whole form is collected before anything is written to disk, so a mid-form Ctrl-C leaves the directory untouched.
Non-interactive run
Section titled “Non-interactive run”dwe init switches to flag-driven mode automatically when stdin/stdout is not a TTY (CI, scripts, pipes), or when you pass --default or --output json. Drive everything from flags:
dwe init --name my-project --prefix acme --service apiOr take all defaults without the form:
dwe init my-project --defaultA positional [name] creates the project in ./<name>/ instead of the current directory. Name resolution precedence is --name → positional [name] → the current directory’s basename.
| Flag | Default | Effect |
|---|---|---|
--name | [name] arg, else cwd basename | project name in workspace.yml |
--prefix | dwe | compose/project prefix |
--brand-title / --tagline / --accent | empty | workspace/styles.yml branding (accent is a 6-digit hex like #2EC3EB) |
--service | app | starter service folder name; "" creates none |
-f, --force | off | recreate an existing project / overwrite existing files |
-d, --default | off | skip the form, take all defaults |
--output json | text | machine-readable report (implies non-interactive) |
With --output json you get a structured report instead of prose:
{ "target": "/abs/path/to/project", "created": ["workspace.yml", "workspace/defaults.yml", "..."], "skipped": [], "symlink_fallback": false, "nested_warning": false}skipped lists files that already existed (and were left alone), symlink_fallback is true when CLAUDE.md had to be written as a copy instead of a symlink, and nested_warning is true when an ancestor workspace.yml was found.
What ends up on disk
Section titled “What ends up on disk”<target>/├─ workspace.yml project.name + project.prefix (+ commented optional fields)├─ compose.yaml base compose file, referenced by defaults.yml├─ .gitignore DWE runtime entries (append-merged if the file already exists)├─ .editorconfig repo conventions (only written if absent)├─ AGENTS.md brief project prompt for AI agents├─ CLAUDE.md → symlink to AGENTS.md (a copy where symlinks are unavailable)├─ .dwe/│ └─ config gitignored, all-commented per-developer user-config template└─ workspace/ ├─ defaults.yml starter service toggle + commented runtime/exports examples ├─ styles.yml branding from the form + commented rest ├─ deploy.yml inert mirror of the built-in deploy pipeline ├─ lifecycle.yml inert mirror of the built-in lifecycle pipeline ├─ info.yml inert mirror of the dashboard config ├─ docker.yml inert mirror of the compose policy ├─ services/app/ │ ├─ service.yml type, container, source hub, icon and info.title active │ └─ deploy.yml inert per-service pipeline skeleton (source → image → bootstrap → render) ├─ templates/ai/default/ │ ├─ manifest.yml ACTIVE AI template pack │ └─ AGENTS.md.tmpl rendered into the service hub by `dwe render ai` └─ tests/ └─ smoke.yml ACTIVE starter scenario (a description, no assertions yet)Four things are worth understanding:
- The override files ship commented out on purpose. DWE pipeline composition is full-replacement — an active
workspace/deploy.ymlreplaces the entire built-in deploy pipeline, so a half-edited file silently drops phases.deploy.yml,lifecycle.yml, andinfo.ymltherefore arrive fully commented, as does the per-serviceservices/app/deploy.yml; the built-in default stays active until you deliberately uncomment and own the whole pipeline.docker.ymlis commented for the same reason but overrides per key, not whole-file: uncommenting one key leaves the built-in defaults for the rest. Each file’s header points at the authoritative default it mirrors. - Two files ship active because they cannot ship commented. A template-pack
manifest.ymlis strict-decoded and must declare at least one entry; the test-scenario loader rejects an empty document outright.workspace/tests/smoke.ymlis therefore a real scenario with adescription:andsteps: []— deliberately assertion-free, because on a fresh scaffoldcompose.yamldeclares no services, so the deploy the scenario wraps fails atstart/upwith “empty compose file” anddwe test run smokereports a failure. The scenario becomes meaningful once your service exists in compose; add steps then. - There are two
AGENTS.mdfiles, and only one is yours to edit. The rootAGENTS.mdis scaffolded once and meant to be edited by hand. The one the AI pack renders lands in the service hub (services/app/, gitignored and absent until the first clone) — editworkspace/templates/ai/default/AGENTS.md.tmplinstead, and re-render withdwe render ai. .gitignoreis merged, not overwritten. If the directory already has a.gitignore,dwe initappends only the DWE runtime lines it is missing, under a# dwemarker. Re-running is a no-op. The entire.dwe/directory is ignored — it holds CLI-managed runtime data and the per-developer.dwe/config, none of which belongs in version control.
After init
Section titled “After init”dwe validate # confirm the fresh project is internally consistentA fresh dwe init is designed to validate clean immediately. The scaffolded app service carries its identity and source hub but nothing to run yet, so configure it — image or build, ports, hosts — in workspace/services/app/service.yml, and add the matching service to compose.yaml, before bringing the stack up:
dwe deploy run # bring the configured stack upTwo pairings the scaffold documents in comments but no validator can check for you:
- A port is display-only until it is exported.
ports:inservice.ymlfeedsdwe statusand info blocks; nothing binds it. Uncomment it together with the matchingexports.envrule inworkspace/defaults.yml, which is what turns it into an environment variablecompose.yamlcan reference.PROJECT,UIDandGIDare injected automatically and must not be redeclared there. - Mount the whole hub, not just the sources.
diris the host directory holding the checkout and everything next to it (build artefacts, caches, tooling state);dir_internalis where that whole directory lands in the container, andwork_dir_internalis where commands run inside it.
From there the usual authoring path applies: add a service, author project commands, brand the dashboard, and uncomment an override file when you genuinely need to reshape a pipeline. For an orientation pass in a fresh project — services, commands, pipeline builtins, diagnostic flags — run dwe docs llms-txt --lang en.
Edge cases
Section titled “Edge cases”- A project already exists. If the target directory already has a
workspace.yml,dwe initwill not silently overwrite it. Interactively it asks you to confirm recreating it (and recreates everything with--forceon yes); non-interactively it stops with an error telling you to pass--force. - Existing
.gitignore/.editorconfig..gitignoreis append-merged;.editorconfigis written only when absent. Neither is clobbered. - Nested projects. If an ancestor directory already has a
workspace.yml,dwe initwarns (nested_warningin JSON) but does not block — sometimes a nested project is what you want. - No starter service.
--service ""scaffolds a valid, service-less project. Everything that references the starter service is dropped with it — its folder, the AI template pack and the starter scenario — so nothing is left dangling. Add services later as folders underworkspace/services/. - Windows symlinks. Where
CLAUDE.mdcannot be symlinked toAGENTS.md, it is written as a verbatim copy and the run notes the fallback.
See also
Section titled “See also”- Joining a DWE project — the other side: a repo that already ships
workspace.yml - workspace.yml reference — the structural identity file and the three-layer merge model
- Adding a service — grow the starter into a real stack
- Brand your project — flesh out the
styles.ymlthe form seeded