Summary
drupal-core and drupal-contrib both permanently disable ddev-router (ddev config global --omit-containers=ddev-router) and bind the web container directly to a host port instead. freeform takes the opposite approach: it keeps ddev-router running and dispatches to each DDEV project by Host header, so multiple projects can share one workspace.
The router-omission approach has a real cost: DDEV only populates DDEV_PRIMARY_URL (and derived vars like DDEV_PRIMARY_URL_WITHOUT_PORT/_PORT) when the router is enabled. Any DDEV add-on or project config that references ${DDEV_PRIMARY_URL} — which is the officially-documented way to do it — silently gets an empty string in these two templates, even though the exact same config works fine outside Coder or in freeform.
This just caused a real bug: #211. amateescu/ddev-drupal-dev's own config.drupal-dev.yaml sets SIMPLETEST_BASE_URL=${DDEV_PRIMARY_URL}, which resolves to "" under drupal-core, so every phpunit functional/JS test failed with "You must provide a SIMPLETEST_BASE_URL environment variable". Fixed for now with a template-level override that forces SIMPLETEST_BASE_URL=http://web (see the PR closing that issue), but that's a point patch for one symptom of a structural mismatch — the next add-on or piece of tooling that trusts DDEV_PRIMARY_URL will hit the same class of bug, silently, in drupal-core/drupal-contrib only.
Proposal
- Switch
drupal-core and drupal-contrib to freeform's router-based Host-header dispatch instead of omit-containers=ddev-router + direct port bind. This makes DDEV_PRIMARY_URL behave normally (matching stock DDEV / local dev), which is exactly the kind of environment-specific surprise this class of bug comes from.
- Consolidate the routing/URL-construction code across all three templates while doing this. Right now each template hand-rolls its own version of: the Coder-domain detection (
VSCODE_PROXY_URI/CODER_AGENT_URL parsing), the drupal-site--$WORKSPACE--$OWNER.$DOMAIN URL construction (this exact snippet is duplicated at least 3 times across drupal-core/template.tf alone, plus again in drupal-contrib/template.tf), and the .ddev/config.coder.yaml / docker-compose.coder-describe.yaml scaffolding. freeform already solves the "multiple URLs behind one router" problem generally; drupal-core/drupal-contrib only ever need the single-project case of it. Folding them onto the same mechanism (e.g. as a shared script or module, similar to modules/claude-remote-control and shared/vscode-extensions.tf) would cut this duplication and mean a routing fix only has to happen once.
Why this matters beyond #211
CLAUDE.md already documents that docs/drupal-core.html, docs/drupal-issue.html, and docs/coder-workspace-name.js build deep links assuming specific parameter/URL shapes, and that shape drift between templates isn't caught by CI. Consolidating the routing logic would shrink the surface area for that kind of drift too, not just fix the DDEV_PRIMARY_URL gap.
Scope note
This is a real architecture change (template routing model + cross-template code structure), not a bug fix — flagging per this repo's OpenSpec conventions (openspec/AGENTS.md) that it should go through a change proposal rather than being done ad hoc.
Summary
drupal-coreanddrupal-contribboth permanently disableddev-router(ddev config global --omit-containers=ddev-router) and bind the web container directly to a host port instead.freeformtakes the opposite approach: it keepsddev-routerrunning and dispatches to each DDEV project by Host header, so multiple projects can share one workspace.The router-omission approach has a real cost: DDEV only populates
DDEV_PRIMARY_URL(and derived vars likeDDEV_PRIMARY_URL_WITHOUT_PORT/_PORT) when the router is enabled. Any DDEV add-on or project config that references${DDEV_PRIMARY_URL}— which is the officially-documented way to do it — silently gets an empty string in these two templates, even though the exact same config works fine outside Coder or infreeform.This just caused a real bug: #211.
amateescu/ddev-drupal-dev's ownconfig.drupal-dev.yamlsetsSIMPLETEST_BASE_URL=${DDEV_PRIMARY_URL}, which resolves to""underdrupal-core, so everyphpunitfunctional/JS test failed with "You must provide a SIMPLETEST_BASE_URL environment variable". Fixed for now with a template-level override that forcesSIMPLETEST_BASE_URL=http://web(see the PR closing that issue), but that's a point patch for one symptom of a structural mismatch — the next add-on or piece of tooling that trustsDDEV_PRIMARY_URLwill hit the same class of bug, silently, indrupal-core/drupal-contribonly.Proposal
drupal-coreanddrupal-contribtofreeform's router-based Host-header dispatch instead ofomit-containers=ddev-router+ direct port bind. This makesDDEV_PRIMARY_URLbehave normally (matching stock DDEV / local dev), which is exactly the kind of environment-specific surprise this class of bug comes from.VSCODE_PROXY_URI/CODER_AGENT_URLparsing), thedrupal-site--$WORKSPACE--$OWNER.$DOMAINURL construction (this exact snippet is duplicated at least 3 times acrossdrupal-core/template.tfalone, plus again indrupal-contrib/template.tf), and the.ddev/config.coder.yaml/docker-compose.coder-describe.yamlscaffolding.freeformalready solves the "multiple URLs behind one router" problem generally;drupal-core/drupal-contribonly ever need the single-project case of it. Folding them onto the same mechanism (e.g. as a shared script or module, similar tomodules/claude-remote-controlandshared/vscode-extensions.tf) would cut this duplication and mean a routing fix only has to happen once.Why this matters beyond #211
CLAUDE.md already documents that
docs/drupal-core.html,docs/drupal-issue.html, anddocs/coder-workspace-name.jsbuild deep links assuming specific parameter/URL shapes, and that shape drift between templates isn't caught by CI. Consolidating the routing logic would shrink the surface area for that kind of drift too, not just fix theDDEV_PRIMARY_URLgap.Scope note
This is a real architecture change (template routing model + cross-template code structure), not a bug fix — flagging per this repo's OpenSpec conventions (
openspec/AGENTS.md) that it should go through a change proposal rather than being done ad hoc.