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—cachenode_modulesvia 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-nothingwhere 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-confignoisepublic/media(or your configured upload path)filesif you store documents outsidepublic
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
httpinternally → 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
intlor raisingmemory_limitfor 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.”