A container setting VIRTUAL_PATH alongside VIRTUAL_HOST is mounted under
that path of that hostname, so a browser-served frontend and its API can share
one origin locally: no CORS, no preflight, one certificate. VIRTUAL_PATH is the
variable nginx-proxy uses for this, and this layer exists to be compatible with
that style of container.
Matching is by path segment. Traefik's PathPrefix is a raw string prefix, so
PathPrefix(/api) would also match /api-docs, while an ingress path prefix splits
on separators and does not. Pairing PathPrefix with Path keeps a mount from
capturing a sibling that merely starts with the same characters, which is what
lets one relative call behave the same locally and once deployed.
Priority is set on mounted routers only. Traefik ranks routers by rule length
when priority is unset, in one table per entrypoint across providers, so giving
host routers an explicit value would re-rank them against routers built from
users' own labels and would replace today's stable exact-versus-wildcard
ordering with an undefined tie. Host routers are therefore emitted exactly as
before, and the field is non-zero where it is set because omitempty would
otherwise drop it and silently restore inherited ordering.
Nothing is stripped, matching both an ingress and nginx-proxy's default;
VIRTUAL_DEST is not supported.
Two failures that were previously silent at debug level now warn: a container
carrying any traefik. label, which is skipped whole so its VIRTUAL_HOST never
routes, and VIRTUAL_PATH with no VIRTUAL_HOST. A third warns when two containers
claim the same host and path, tracked across events rather than only at startup,
because the proxy is normally running before a stack starts.
The certificate commands reject an argument containing a path, since a
certificate covers a hostname and the filename helper would otherwise fold the
path in and half succeed. self-test now exercises a mounted path over both
schemes, comparing the response body: without the path route the request falls
through to the hostname's container, which answers 200 and would hide it.
Also fixes the CORS example, which could never have worked because its
middleware label made the layer skip the container.
Refs: #113
Assisted-by: claude-code/claude-opus-5
User description
Closes #113. Implements the specification merged in #114.
What it does
A container setting
VIRTUAL_PATHalongsideVIRTUAL_HOSTis mounted under that path of that hostname, so two containers can share one domain:myapp.local/reaches the frontend,myapp.local/api/...reaches the API, and the page can call/api/...with no host in front of it. No CORS, no preflight, one certificate.VIRTUAL_PATHis nginx-proxy's variable for this, which is why it was chosen over inventing syntax insideVIRTUAL_HOST:VIRTUAL_HOSTparsing is untouched, each container keeps its ownVIRTUAL_PORT, and the~-regex form cannot have a slash reinterpreted.Two routing decisions worth reviewing
Matching is by segment. Traefik's
PathPrefixis a raw string prefix, soPathPrefix(/api)also matches/api-docs. An ingresspathType: Prefixsplits on separators and does not. Since the point is that one relative call behaves the same locally and once deployed, the emitted rule is:Priority is set on mounted routers only. Traefik ranks routers by rule length when priority is unset, in one table per entrypoint, across providers. Setting a priority on the host routers this layer already emits would re-rank them against routers built from users' own
traefik.*labels, and would replace today's stable exact-versus-wildcard ordering with an undefined tie. So host routers are emitted byte-identically to before, and only mounted routers carry a value. That value is non-zero deliberately: withomitemptya zero would be dropped and silently restore inherited ordering, which a test pins.Three silent failures that now warn
None are caused by this feature; the first two are simply easier to hit with it.
traefik.label is skipped whole, so itsVIRTUAL_HOSTnever routes. This is intentional precedence, but a middleware label alone triggers it. It was debug-level, so invisible by default.VIRTUAL_PATHwith noVIRTUAL_HOSTexposes nothing at all.Also fixed
Example 7 in
examples/applications.ymlcould never have worked: its CORS middleware label made the layer skip the container, soVIRTUAL_HOST=api.locproduced no route and the routers it referenced did not exist. It now declares its own routers.AGENTS.mdsaid the repository has no unit tests and thatmake testis the verification step. There are five_test.gofiles, andmake testruns only the integration suite.Tests
Unit (
cmd/dinghy-layer/main_test.go): every accepted and rejected path form, the rule asserted whole, the emitted YAML asserted for both the presence and the absence of priority, and the ordering against host and wildcard rules. I mutation-checked the three that matter: replacing the matcher with a naivePathPrefixfails 5 assertions, dropping the priority fails 6, removing the path validation fails 8.Integration (
test/test.sh): two containers sharing a hostname with distinct bodies, since status codes cannot distinguish a working mount from a fall-through. Covers the root, the mount, a nested path,/api-docsnot being captured, both schemes, a wildcard not outranking the mount, and the documented fall-through when the mounted container stops.Diagnostic:
self-teststarts a mounted container and checks it over both schemes by body.Security note
The rule is assembled by string formatting, so a path containing a backtick could close the matcher and append arbitrary routing syntax. Validation rejects anything that is not a single plain path, and there is a test for exactly that input.
PR Type
Enhancement, Tests, Documentation, Bug fix
Description
Add
VIRTUAL_PATHshared-host path routingValidate paths and prioritize mounted routers
Warn about ignored and duplicate routes
Cover routing, diagnostics, docs, and examples
Diagram Walkthrough
File Walkthrough
3 files
Generate validated prioritized virtual path routesAdd router priority YAML configuration supportValidate certificate domains and path self-test2 files
Test path parsing routing priorities and claimsExercise virtual path routing end to end3 files
Document virtual paths and test suite guidanceRecord virtual path routing release changesDocument shared-domain `VIRTUAL_PATH` configuration1 files
Fix CORS example and demonstrate path routing