feat(proxy): run multiple local shops in parallel behind a shared proxy - #1208
feat(proxy): run multiple local shops in parallel behind a shared proxy#1208Tomasz Turkowski (tturkowski) wants to merge 22 commits into
Conversation
4b3b422 to
ab10413
Compare
|
Whats definitively missing here is:
|
Dev watchers through the shared proxyA quick summary of how the admin/storefront watchers can work behind the shared reverse proxy. Admin watcher — works as-is, no code changesThe admin is Vite-only. Vite works out its own HMR connection from the page it's loaded on, so all that was needed:
You open Storefront watcher (now) — webpack + a small runtime patchThe storefront's classic watcher (HMR + webpack, Rather than patching vendor file, we inject a tiny preload script when launching the watcher (Node Result: the storefront watcher runs fully through the proxy - multiple shops in parallel, clean port-free hostnames, no exposed ports, and no change to Shopware or the shop. You browse Tradeoff: it's a runtime patch — clever but hidden, and it leans on the internals of Storefront watcher (future) — Vite, the clean pathFrom 6.7.11 the storefront also ships a Vite dev server. To make that work behind a reverse proxy we need a small, fully backward-compatible contribution to
With that, the storefront watcher - once enabled, works at the shop's own URL ( Plan proposal:
|
|
btw because of excactly those REASONS I DONT WANT TO have those watchers directly inside Shopware. we're like now screwed |
Adds `shopware-cli project proxy` (setup/up/down/list/status/verify/ teardown): a shared Traefik container routes stable hostnames like https://shop1.shopware.local to local projects, so shops publish no host ports and any number can run at once. - embedded wildcard DNS server (x/net/dns) on 127.0.0.1:53535, wired via /etc/resolver (macOS) or systemd-resolved (Linux) by a one-time `proxy setup` with a single sudo ceremony (--domain, --skip-trust) - trusted HTTPS out of the box: mkcert-compatible local CA, per-project wildcard SANs, trust-store install via smallstep/truststore - proxy mode is a marker-guarded compose.override.yaml (ports cleared with !reset, requires Compose >= 2.24); the base compose.yaml stays untouched, so `project dev` and manual docker compose keep working in both modes - `up` points APP_URL, the sales channel domain and the url keys in .shopware-project.yml at the proxy; `down` restores everything exactly - `verify` checks the whole chain bottom-up with actionable hints, including guidance when sudo is blocked or systemd-resolved is missing - docs/proxy.md explains the architecture, decisions and trade-offs Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
54c9305 to
cae4216
Compare
There was a problem hiding this comment.
Pull request overview
This PR introduces a new shopware-cli project proxy command group and supporting infrastructure to run multiple local Shopware projects in parallel behind a shared Traefik reverse proxy, using stable per-project hostnames with local DNS resolution and trusted HTTPS.
Changes:
- Adds a new
internal/proxysubsystem (DNS daemon, resolver configuration, Traefik management, verification, trust store integration, registry/settings state). - Implements proxy-mode Docker Compose overrides (marker-guarded
compose.override.yaml) to remove fixed host ports and route by hostname via Traefik. - Integrates proxy awareness into
project create,project dev, the dev TUI overview, and storefront watcher routing.
Reviewed changes
Copilot reviewed 69 out of 70 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| internal/tui/dev/tab_overview_health.go | Adds proxy-related setup health checks to the TUI overview health panel. |
| internal/tui/dev/tab_overview_format_test.go | Adds tests for overview formatting helpers used by the TUI. |
| internal/tui/dev/model.go | Adds proxy fallback handling and an interactive proxy setup execution path in the TUI. |
| internal/system/dockername.go | Introduces Docker Compose project-name validation helper. |
| internal/proxy/wsl_resolver_test.go | Adds unit tests for WSL DNS resolver guidance text. |
| internal/proxy/windows_access.go | Adds WSL/Windows browser access guidance and hostname list generation. |
| internal/proxy/windows_access_test.go | Adds tests for Windows access guidance and hostname generation. |
| internal/proxy/verify.go | Implements proxy verify bottom-up checks (Docker → DNS → OS resolver → Traefik → trusted HTTPS). |
| internal/proxy/verify_windows.go | Windows stub for OS-level resolution check. |
| internal/proxy/verify_test.go | Adds tests for verification hints and probe-hostname generation. |
| internal/proxy/verify_linux.go | Linux OS-resolution check via getent hosts. |
| internal/proxy/verify_darwin.go | macOS OS-resolution check via dscacheutil. |
| internal/proxy/trust.go | Implements CA trust installation flow and user guidance (mkcert/truststore). |
| internal/proxy/trust_test.go | Adds tests for trust-blocked guidance content. |
| internal/proxy/traefik.go | Adds Traefik container/network lifecycle management and hostname alias reconciliation. |
| internal/proxy/traefik_test.go | Adds tests for Traefik dynamic config writing and alias helpers. |
| internal/proxy/stats.go | Adds proxy instance stats collection for the TUI (shop count + memory sum). |
| internal/proxy/stats_test.go | Adds tests for parsing Docker memory usage strings. |
| internal/proxy/statedir.go | Adds a shared state directory helper for proxy state files. |
| internal/proxy/settings.go | Adds machine-wide proxy settings (base domain) with validation and persistence. |
| internal/proxy/settings_test.go | Adds tests for settings validation and persistence round-trips. |
| internal/proxy/resolver.go | Adds resolver status types and Linux-without-systemd-resolved guidance text. |
| internal/proxy/resolver_windows.go | Windows resolver stubs reporting unsupported behavior. |
| internal/proxy/resolver_linux.go | Linux systemd-resolved split-DNS configuration (configure/unconfigure + guidance). |
| internal/proxy/resolver_linux_test.go | Adds tests for Linux resolver blocked guidance content. |
| internal/proxy/resolver_darwin.go | macOS /etc/resolver configuration (configure/unconfigure + guidance). |
| internal/proxy/resolver_darwin_test.go | Adds tests for macOS resolver blocked guidance content. |
| internal/proxy/registry.go | Adds registry state for registered projects and restore metadata. |
| internal/proxy/registry_test.go | Adds registry behavior tests (upsert/remove/find/round-trip). |
| internal/proxy/projectconfig.go | Adds comment-preserving YAML URL rewrite/restore logic for .shopware-project.yml. |
| internal/proxy/projectconfig_test.go | Adds tests for URL rewrite/restore semantics and missing-file behavior. |
| internal/proxy/hostname.go | Adds proxy hostname derivation (from config URL or directory name). |
| internal/proxy/hostname_test.go | Adds tests for hostname derivation edge cases. |
| internal/proxy/docker.go | Adds Docker Compose version check for !reset support and docker runner helper. |
| internal/proxy/dns.go | Adds embedded DNS server implementation and direct-query helper for verification/tests. |
| internal/proxy/dns_test.go | Adds tests for DNS zone behavior and garbage packet handling. |
| internal/proxy/dns_daemon.go | Adds non-Windows DNS daemon spawning/management via self re-exec and PID/state files. |
| internal/proxy/dns_daemon_windows.go | Adds Windows stubs and shared “not supported” error for DNS daemon operations. |
| internal/proxy/cert.go | Adds mkcert-compatible CA/cert management and SAN host list generation. |
| internal/proxy/cert_test.go | Adds tests for certificate creation, idempotency, and regeneration triggers. |
| internal/proxy/canonical.go | Adds canonical project-root resolution (symlink normalization). |
| internal/mkcert/mkcert.go | Adds BSD-licensed mkcert-derived CA/certificate implementation as an internal library. |
| internal/mkcert/mkcert_test.go | Adds tests for CAROOT behavior, CA creation/reuse, cert issuance, and keyless mode. |
| internal/mkcert/LICENSE | Adds the mkcert BSD license text for the adapted code. |
| internal/extension/storefront_watch.go | Adds proxy-mode support for the deprecated storefront hot-proxy watcher via env + Node preload. |
| internal/extension/storefront_hmr_patch.cjs | Adds a managed Node preload patch to rewrite webpack-dev-server websocket target behind the proxy. |
| internal/executor/docker.go | Improves watcher shutdown by SIGINTing the full in-container process tree (not just the wrapper process). |
| internal/envfile/upsert.go | Adds an env-file “upsert var” helper for surgical .env updates. |
| internal/envfile/upsert_test.go | Adds tests for env var upsert and read behavior. |
| internal/docker/compose.go | Adds a YAML boolean-node helper used by proxy compose override generation. |
| internal/docker/compose_test.go | Adds a regression test ensuring base compose output remains non-proxy (ports/labels absent). |
| internal/docker/compose_override.go | Adds generation + write/remove for marker-guarded proxy compose overrides with Traefik routes. |
| internal/docker/compose_override_test.go | Adds extensive tests for override content and safety checks (refuse user override files). |
| go.mod | Adds github.com/smallstep/truststore and an indirect plist dependency for trust installation. |
| go.sum | Adds checksums for new module dependencies. |
| docs/proxy.md | Adds end-to-end architecture/design documentation for the shared proxy feature set. |
| cmd/root.go | Treats ErrProxyNotRegistered as a user-facing error (exit 1 without extra logging). |
| cmd/project/project_storefront_watch.go | Routes storefront watcher through proxy hostname when the project is proxied. |
| cmd/project/project_proxy_verify.go | Adds project proxy verify command and shared output printer for verification steps. |
| cmd/project/project_proxy_test.go | Adds tests for local-domain choice resolution and Shopware command availability detection. |
| cmd/project/project_proxy_setup.go | Adds project proxy setup and teardown, including DNS/trust installation and verification. |
| cmd/project/project_proxy_list.go | Adds project proxy list and status commands with running-instance detection and links. |
| cmd/project/project_proxy_dns_serve.go | Adds hidden internal subcommand used as the DNS daemon re-exec target. |
| cmd/project/project_dev.go | Bootstraps proxy infra for proxy-mode projects with a non-blocking fallback to port mode. |
| cmd/project/project_dev_test.go | Adds tests for proxy-project detection and local-domain hostname normalization. |
| cmd/project/project_create.go | Adds --local-domain support, inline (prompted) one-time setup option, and base-domain lookup. |
| cmd/project/project_create_install.go | Writes proxy hostname URLs into the created project config and updates create summary output. |
| cmd/project/project_create_form.go | Extends interactive create form to prompt for local domains and optional one-time machine setup. |
Suppressed comments (1)
internal/proxy/hostname.go:33
- ProjectHostname can produce invalid DNS hostnames when the project directory contains underscores (Docker Compose allows them, DNS labels do not). Sanitizing underscores to dashes here keeps hostnames valid and matches the behavior in project create (localDomainHostname).
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…cuit verify on Windows
Works same way as it works on mac or windows, we do serve dns and in wsl we redirect domain traffic to our dns that resolves to 127.0.0.1 and then traefik takes care about the rest. WSL users who want to access WSL-running shops from Windows browser would need to one time setup Windows's hosts file.
Made them work, but it was a hard part. See comment above with details.
Made that work, containers will resolve from domain to the container, no need of container names usage. |

What changed?
New command group
shopware-cli project proxy— run any number of local shops in parallel under stable hostnames, instead of everyone fighting over127.0.0.1:8000.proxy setup--domain,--skip-trust)proxy up/downproxy list/statusproxy verifyproxy teardownUnder the hood: one shared Traefik container routes by hostname (shops publish no host ports at all), a tiny DNS server embedded in the binary answers
*.shopware.local → 127.0.0.1, and an mkcert-compatible local CA provides trusted HTTPS. Proxy mode is a marker-guardedcompose.override.yaml— the basecompose.yamlstays untouched, soproject devand manualdocker composekeep working.uppointsAPP_URL, the sales-channel domain and the project config at the proxy;downrestores every value exactly.➡️ Architecture, design decisions and trade-offs:
docs/proxy.mdWhy?
The dev environment publishes fixed host ports, so a second shop can't start — anyone working on multiple projects juggles ports or stops shops. Routing by hostname removes the conflict by construction, and trusted HTTPS matters for testing payment providers locally.
How was this tested?
go test ./...green,golangci-lint run ./...— 0 issuesup/down/teardowncycles with byte-identical restore of.shopware-project.yml,.env.localand the sales-channel domain;verifyladder validated against a real corporate sudo-block scenarioRelated issue or discussion
Closes #1094, related: #939