Notes / March 20, 2026

Running Shopware on Platform.sh

Builds, mounts, workers, and environment parity for a PHP + JS stack that behaves like production.

Shopware 6 is a Symfony PHP application plus Storefront tooling (often Node/Vite), background workers, and ephemeral writable state (var/, uploads, JWT keys). Platform.sh treats infrastructure as versioned config beside your app—ideal when you want develop to behave like production without snowflake servers.

This note captures what we optimized for on a multi-plugin shop (tax, search, ERP hooks)—the boring details that keep deploys boring (in a good way).


1. Repository layout and build

.platform.app.yaml (conceptual responsibilities)

  • Build hook: composer install --no-dev --prefer-dist --optimize-autoloader (or with dev on non-prod types).
  • Node build (if compiling storefront in CI): install Node LTS, npm ci, run theme/storefront build once—cache node_modules via Platform’s build cache when possible.
  • Deploy hook: bin/console assets:install, bin/console theme:compile, bin/console cache:clear (warm), migrations guarded by idempotency (--all-or-nothing where supported).

Why split build vs deploy
Build must be reproducible; deploy must be fast. Long webpack runs belong in build, not on every rolling deploy.


2. Services and parity

Match service types across environments:

Service Notes
MariaDB/MySQL Size disk for imports + indexer temp tables
Redis Sessions + cache; separate DB index if you isolate cache vs session
RabbitMQ (optional) Shopware async message bus; scale consumer count per env
OpenSearch/ES (optional) If search is external, provision same major version in staging

Disk
Shopware generates exports, logs, and media derivatives—undersized disk is the #1 midnight page.


3. Mounts (writable paths)

Platform containers are read-only except declared mounts. Typical Shopware mounts:

  • var — logs, cache, jwt, theme-config noise
  • public/media (or your configured upload path)
  • files if you store documents outside public

Gotcha: forgetting a mount → runtime exception on upload or JWT generation only in prod—always smoke-test file upload after first deploy.


4. Cron and workers

  • Message consumers: bin/console messenger:consume async (names vary by project)—run as supervised worker with restart policy, not a one-shot cron unless traffic is tiny.
  • Scheduled tasks: Shopware scheduler + Platform cron—align timezone to UTC internally, display in merchant TZ.
  • Queue depth alerts: hook Platform metrics or external APM when depth grows (ERP spikes).

5. Shopware-specific configuration drift

Sales channel domains

Each Platform environment gets a new URL. We scripted:

bin/console system:config:set core.loginRegistration.doubleOptInDomain -- ... # example pattern

…for storefront, admin, and API base URLs so:

  • Password reset emails link correctly
  • Avalara / PSP webhooks hit the right environment (never staging URLs in prod keys)

Trusted proxies / TLS termination

Traffic arrives at PHP with X-Forwarded-For / X-Forwarded-Proto. Symfony’s trusted_proxies and trusted_headers must include Platform’s edge—otherwise:

  • OAuth return URLs use http internally → auth failures
  • Rate limits see one IP → wrong throttling

6. Secrets and variables

  • Store license keys, Avalara credentials, and PSP secrets in Platform variables (encrypted), not git.
  • Use different Avalara companies or sandbox flags per environment—we caught a staging order posting tax to prod Avalara once; fixed with env-scoped keys + automated smoke test.

7. Developer workflow wins

  • Branch → environment mapping let QA open tickets with URLs that matched PR names.
  • Disk / PHP extension changes lived in YAML reviewed in PR—no “ticket to infra” latency for adding intl or raising memory_limit for indexer jobs.

Resume framing

“I owned Shopware on Platform.sh: build hooks, mounts, workers, sales-channel URL automation, and edge/proxy correctness for tax and payments.”