Notes / March 13, 2026

Multiple Shopware storefronts on your laptop

Hosts file, sales channels, trusted proxies, and Vite so B2C and B2B behave correctly before deploy.

When a project runs B2C and B2B (or US vs. CA, or dealer vs. consumer) as separate sales channels, http://127.0.0.1:8000 alone is not enough. You need the browser to send a Host header that matches the sales channel’s domain mapping—otherwise you are always testing the default channel and shipping bugs reproduce only in staging.

This note is the recipe we used so QA and engineers could switch channels without editing .env every hour.


1. Why hosts file + real DNS-shaped names

Shopware resolves the active sales channel using request URL (and related config). Using:

  • b2c.test
  • b2b.test

…pointed to 127.0.0.1 gives you distinct Host headers while staying offline.

mkcert / TLS (optional but realistic)
Some payment or cookie flows behave differently on HTTPS. mkcert local certs for *.test remove “works on HTTP only” surprises.


2. Admin configuration checklist

  1. Sales channels → domain
    Add each local host to the correct channel. Watch for http vs https—must match how you browse.

  2. Language + currency per channel
    Wrong defaults cause “tax looks fine on B2C but B2B is EUR” confusion that is not a tax bug.

  3. Snippet / theme assignment
    If B2B uses a different theme or snippet set, verify theme:dump / storefront config points at the right compile target.

  4. Trusted proxies
    Behind Vite dev proxy or Caddy, Symfony must trust forwarded headers or redirects loop.


3. Storefront + Vite

Problem: HMR websocket defaults to one host; second host breaks hot reload.

Mitigations we used

  • Drive APP_URL / storefront proxy config from one primary dev URL for asset compilation.
  • For secondary hosts, accept full page reload during dev or run a second npm run dev with explicit --host / port mapping (document the trade-off for the team).

Cookies

  • Do not set cookie domain to .test unless you intentionally share sessions between b2c.test and b2b.test.
  • Isolated carts prevented “I added SKU on B2C and it appeared in B2B cart” false positives.

4. Debugging: prove the right channel

Add a temporary Twig snippet or toolbar entry (only dev) printing:

  • context.salesChannel.id
  • context.salesChannel.name
  • Active language id

Compare across hosts on the same product URL pattern.

CLI parity

bin/console sales-channel:list

Cross-check UUIDs against what the storefront shows—admin typos here burn hours.


5. API and headless

If Store API clients pass sw-access-key only, they may bypass host-based resolution—ensure integration tests still set sales channel header (sw-sales-channel-id) where the API requires it.


6. Docker alternative (same ideas)

If you use docker-compose instead of bare PHP:

  • Map multiple extra_hosts or Traefik routes (b2c.localhost, b2b.localhost).
  • Mount var/ per container user so theme:compile does not permission-fight.

Resume framing

“Set up multi-host local Shopware with correct sales-channel resolution, Vite-aware dev limits, and documented cookie/session boundaries—reduced channel-specific bugs caught late in QA.”