Shopware in the fast lane – Shopware 6 with Kubernetes
12 min read

Shopware 6, auto-scalable and performant with Kubernetes
In this article we show you how Shopware 6 can be run on Kubernetes and why this makes sense. We show you code excerpts and a brief guide with tips and tricks.
Why should Shopware even run in a clustered environment?
Scalability
When load increases, it can easily be scaled manually or automatically.
Performance
Every building block improves the shop's performance.
Fault tolerance
Since the components run multiple times, the failure of one component does not affect the stable operation of the application.
Self-healing
If a component in Kubernetes crashes, it can be restarted automatically.
Efficient
Through flexible scaling no unnecessary provisioning of hardware is needed. This means that less hardware is required than in conventional operation. This leads to a lower "carbon footprint".
But there are also drawbacks:
Complexity: due to the sheer number of different components, the environment is no longer trivial in its architecture.
Expert knowledge: managing the environment requires knowledge of many different components. We're happy to help in an advisory capacity, support with installation, or even take over operations for you.
Kubernetes vs managed Kubernetes
Kubernetes is one of the most modern technologies in web hosting and requires particular expert knowledge. This covers both operating Kubernetes itself and running an application within Kubernetes. By orchestrating applications into small containers, installation, operation and the required expertise are only partly comparable to classic hosting. For this reason, cloud providers already offer Kubernetes as a Service (Managed Kubernetes) to outsource the necessary expertise. We've had good experience with this setup and use the Managed Kubernetes cluster in OVH Cloud for our use cases.
Configuring managed Kubernetes with OVHcloud
Managed Kubernetes is offered by OVHcloud within the OVH public cloud. To use it, an account must be created in the OVH Manager.
(https://www.ovh.com/manager/public-cloud)Managed Kubernetes can currently be run in France, Germany, England, Australia and Russia.
Configuration is largely self-explanatory and, besides selecting the location, requires
Kubernetes version
OVH network in which it is to run
Size of the worker nodes to be used within Kubernetes
Size of the node pool
Billing model (monthly, hourly)

Featured environment
In our example, we're looking at a containerised environment running on Kubernetes.
For the reasons outlined above, the example implementation runs in a managed Kubernetes cluster on OVH Cloud. We use Shopware 6.4 as the base, use Redis to take load off the database, run MySQL/MariaDB for the database, store all required files in S3 object storage, and cover further optimisation options such as an HTTP cache via Varnish and a search setup based on Elasticsearch.

Configuration of individual components
Shopware 6.4
With its modern architecture, Shopware 6.4 is well suited for this setup. Settings are configured via the API, files that are shared across instances can typically be externalized, and read-only database instances can be configured directly. Since version 6.4, Shopware also supports PHP 8, which offers even higher performance than PHP 7.4.
Note
We always recommend using the latest PHP version supported by your Shopware version. It is important to ensure that the PHP version is still actively receiving updates. If it is no longer supported, you should immediately upgrade Shopware to a version that is compatible with a supported PHP version.
As a general rule, upgrading your PHP version improves performance and minimizes the risk of security vulnerabilities.
Note
In a Kubernetes environment, or other container-based environments where the visitor does not communicate directly with the web server, TRUSTED PROXIES must be configured.
The description of Trusted Proxies can be found in the Shopware documentation for Varnish, but it is also required in Kubernetes environments even without Varnish.
# config/packages/framework.yaml
framework:
# ...
trusted_proxies: '%env(TRUSTED_PROXIES)%'
# .env
...
TRUSTED_PROXIES=127.0.0.1,{Proxy-IP}
...
Note
It is recommended to create a custom container image tailored to your environment. If you need support with this, please contact us.
Redis
Redis is an open-source in-memory key-value database. Because Redis operates entirely in memory, all operations are extremely fast. In Shopware 6, various caches can be configured to optimize performance. Redis is an ideal storage backend for application, object, and HTTP caches. Depending on your requirements, Redis can be used for each of these cache types. Implementation details can be found in the vendor documentation.
Note
Configuration examples are referenced from the vendor documentation to ensure the information remains up to date.
Redis as a session storage backend:
# config/packages/redis.yml
framework:
session:
handler_id: "redis://host:port"
Redis as cart storage:
# config/packages/cart.yml
shopware:
cart:
redis_url: 'redis://localhost:6379/0?persistent=1'
Example Redis deployment in Kubernetes:
$ helm repo add bitnami https://charts.bitnami.com/bitnami
$ helm install my-redis bitnami/redis
Database
Alongside the web server, the database is the second fundamental component of a Shopware setup. Similar to PHP, newer database versions generally provide performance improvements. Therefore, you should always review the documentation to determine which versions of your preferred database are supported.
Note
We always recommend running the database separately from the web server on dedicated system(s). You should also review the size of your Shopware database and allocate enough memory to the database instance so that ideally the entire database, or at least large portions of it, can be loaded into memory.
As mentioned earlier, Shopware 6 directly supports the configuration of MySQL/MariaDB clusters, including the use of read-only replicas. Anyone interested in exploring the possibilities of a MySQL cluster can test the Managed Databases for MySQL offered by OVHcloud. Database integration is typically configured during installation and then stored in the .env file, as shown in the following example:
# .env
...
DATABASE_URL="mysql://{DB-USER}:{DB-Password}@{DB-IP}:{DB-Port}/{DB-NAME}" # Must always be present
DATABASE_REPLICA_0_URL="mysql://{DB-USER}:{DB-Password}@{DB-IP}:{DB-Port}/{DB-NAME}" # For read-only instances in a database cluster
...
S3 Object Storage
Data that must be available on all application servers can be efficiently stored in S3 object storage. One advantage is that these assets can be retrieved in parallel during page loads because the browser can establish a separate connection for them. The object storage typically contains themes, sitemaps, and assets.
Note
For this use case, the S3 object storage must provide sufficient performance. Otherwise, write-intensive operations such as theme compilation may take an extremely long time.
In our environment, we use OVH High Performance Buckets for this purpose, as they provide excellent performance and can be used immediately with Shopware thanks to their Amazon S3 compatibility.
Example integration of OVH High Performance Buckets in the SBG region with Shopware 6:
# config/packages/shopware.yaml
shopware:
filesystem:
private:
type: "amazon-s3"
config:
bucket: "{S3-Bucket-Name}"
endpoint: "https://s3.sbg.cloud.ovh.net"
region: 'SBG'
credentials:
key: {S3-Key}
secret: {S3-Secret}
options:
visibility: "private"
public:
type: "amazon-s3"
url: 'https://{S3-Bucket-Name}.s3.sbg.perf.cloud.ovh.net'
config:
bucket: "{S3-Bucket-Name}"
region: "sbg"
endpoint: "https://s3.sbg.perf.cloud.ovh.net"
root: "/"
credentials:
key: {S3-Key}
secret: {S3-Secret}
options:
visibility: "public"
theme:
...
asset:
...
sitemap:
...
External HTTP cache - Varnish
If Shopware's built-in HTTP cache is no longer sufficient or you want to reduce the load on the application servers, Shopware supports the use of an external reverse HTTP cache. In our case, Varnish is used, which also stores its cache in memory. An upstream Varnish instance reduces load on application servers because many requests can be answered directly from the cache. This is especially beneficial during automatic scaling events.
Note
In a Kubernetes environment, or other container-based environments where the visitor does not communicate directly with the web server, TRUSTED PROXIES must be configured.
The description of Trusted Proxies can be found in the Shopware documentation for Varnish, but it is also required in Kubernetes environments even without Varnish.
# .env
...
SHOPWARE_HTTP_CACHE_ENABLED=1
TRUSTED_PROXIES=127.0.0.1,{Proxy-IP}
...
To integrate an upstream Varnish instance, the following file must be created.
# config/packages/storefront.yaml
storefront:
csrf:
enabled: true
# The internal Shopware http cache replaces the csrf token on the fly. This can't be done in Reverse proxy. So we use ajax to get an csrf token
mode: ajax
reverse_proxy:
enabled: true
ban_method: "BAN"
# This needs to point to your varnish hosts
hosts: [ "http://{IP-Varnish-System}" ]
# Max parallel invalidations at same time for a single worker
max_parallel_invalidations: 3
# Redis Storage for the http cache tags
redis_url: "redis://{IP-Redis-System}"
Shopware Varnish configuration:
vcl 4.0;
import std;
# You should specify here all your app nodes and use round robin to select a backend
backend default {
.host = "<app-host>";
.port = "80";
}
# ACL for purgers IP. (This needs to contain app server ips)
acl purgers {
"127.0.0.1";
"localhost";
"::1";
}
sub vcl_recv {
# Mitigate httpoxy application vulnerability, see: https://httpoxy.org/
unset req.http.Proxy;
# Strip query strings only needed by browser javascript. Customize to used tags.
if (req.url ~ "(?|&)(pk_campaign|piwik_campaign|pk_kwd|piwik_kwd|pk_keyword|pixelId|kwid|kw|adid|chl|dv|nk|pa|camid|adgid|cx|ie|cof|siteurl|utm_[a-z]+|ga|gclid)=") {
# see rfc3986#section-2.3 "Unreserved Characters" for regex
set req.url = regsuball(req.url,
"pk_campaign|piwik_campaign|pk_kwd|piwik_kwd|pk_keyword|pixelId|kwid|kw|adid|chl|dv|nk|pa|camid|adgid|cx|ie|cof|siteurl|utm[a-z]+|_ga|gclid)=[A-Za-z0-9-_.~]+&?", "");
}
set req.url = regsub(req.url, "(?|?&|&)$", "");
# Normalize query arguments
set req.url = std.querysort(req.url);
# Make sure that the client ip is forward to the client.
if (req.http.x-forwarded-for) {
set req.http.X-Forwarded-For = req.http.X-Forwarded-For + ", " + client.ip;
} else {
set req.http.X-Forwarded-For = client.ip;
}
# Handle BAN
if (req.method == "BAN") {
if (!client.ip ~ purgers) {
return (synth(405, "Method not allowed"));
}
ban("req.url ~ "+req.url);
return (synth(200, "BAN URLs containing (" + req.url + ") done."));
}
# Normalize Accept-Encoding header
# straight from the manual: https://www.varnish-cache.org/docs/3.0/tutorial/vary.html
if (req.http.Accept-Encoding) {
if (req.url ~ ".(jpg|png|gif|gz|tgz|bz2|tbz|mp3|ogg)$") {
# No point in compressing these
unset req.http.Accept-Encoding;
} elsif (req.http.Accept-Encoding ~ "gzip")
{ set req.http.Accept-Encoding = "gzip";
} elsif (req.http.Accept-Encoding ~ "deflate"){
set req.http.Accept-Encoding = "deflate";
} else {
# unkown algorithm
unset req.http.Accept-Encoding;
}
}
if (req.method != "GET" &&
req.method != "HEAD" &&
req.method != "PUT" &&
req.method != "POST" &&
req.method != "TRACE" &&
req.method != "OPTIONS" &&
req.method != "PATCH" &&
req.method != "DELETE") {
/* Non-RFC2616 or CONNECT which is weird. /
return (pipe);
}
# We only deal with GET and HEAD by default
if (req.method != "GET" && req.method != "HEAD") {
return (pass);
}
# Don't cache Authenticate & Authorization
if (req.http.Authenticate || req.http.Authorization) {
return (pass);
}
# Always pass these paths directly to php without caching
# Note: virtual URLs might bypass this rule (e.g. /en/checkout)
if (req.url ~ "^/(checkout|account|admin|api)(/.)?$") {
return (pass);
}
return (hash);
}
sub vcl_hash {
# Consider Shopware http cache cookies
if (req.http.cookie ~ "sw-cache-hash=") {
hash_data("+context=" + regsub(req.http.cookie, "^.?sw-cache-hash=([^;]);.$", "\1"));
} elseif (req.http.cookie ~ "sw-currency=") {
hash_data("+currency=" + regsub(req.http.cookie, "^.?sw-currency=([^;]);.$", "\1"));
}
}
sub vcl_hit {
# Consider client states for response headers
if (req.http.cookie ~ "sw-states=") {
set req.http.states = regsub(req.http.cookie, "^.?sw-states=([^;]);.$", "\1");
if (req.http.states ~ "logged-in" && obj.http.sw-invalidation-states ~ "logged-in" ) {
return (pass);
}
if (req.http.states ~ "cart-filled" && obj.http.sw-invalidation-states ~ "cart-filled" ) {
return (pass);
}
}
}
sub vcl_backend_response {
# Fix Vary Header in some cases
# https://www.varnish-cache.org/trac/wiki/VCLExampleFixupVary
if (beresp.http.Vary ~ "User-Agent") {
set beresp.http.Vary = regsub(beresp.http.Vary, ",? User-Agent ", "");
set beresp.http.Vary = regsub(beresp.http.Vary, "^, *", "");
if (beresp.http.Vary == "") {
unset beresp.http.Vary;
}
}
# Respect the Cache-Control=private header from the backend
if (
beresp.http.Pragma ~ "no-cache" ||
beresp.http.Cache-Control ~ "no-cache" ||
beresp.http.Cache-Control ~ "private"
) {
set beresp.ttl = 0s;
set beresp.http.X-Cacheable = "NO:Cache-Control=private";
set beresp.uncacheable = true;
return (deliver);
}
# strip the cookie before the image is inserted into cache.
if (bereq.url ~ ".(png|gif|jpg|swf|css|js|webp)$") {
unset beresp.http.set-cookie;
}
# Allow items to be stale if needed.
set beresp.grace = 6h;
# Save the bereq.url so bans work efficiently
set beresp.http.x-url = bereq.url;
set beresp.http.X-Cacheable = "YES";
# Remove the exact PHP Version from the response for more security
unset beresp.http.x-powered-by;
return (deliver);
}
sub vcl_deliver {
## we don't want the client to cache
set resp.http.Cache-Control = "max-age=0, private";
# remove link header, if session is already started to save client resources
if (req.http.cookie ~ "session-") {
unset resp.http.Link;
}
# Set a cache header to allow us to inspect the response headers during testing
if (obj.hits > 0) {
unset resp.http.set-cookie;
set resp.http.X-Cache = "HIT";
} else {
set resp.http.X-Cache = "MISS";
}
# Remove the exact PHP Version from the response for more security (e.g. 404 pages)
unset resp.http.x-powered-by;
# invalidation headers are only for internal use
unset resp.http.sw-invalidation-states;
set resp.http.X-Cache-Hits = obj.hits;
}
Note
If the website is protected with Basic Auth, for example during a testing phase, authentication must occur before Varnish; otherwise, the content will not be cached.
Elasticsearch
Elasticsearch can be useful when the shop contains a very large number of categories or product variants. Elasticsearch requires dedicated Shopware components. The installation and configuration process can be found in the Shopware documentation.
Note
Before implementing Elasticsearch, you should carefully evaluate whether it is actually necessary. Shopware also offers an Enterprise Search solution that is likewise based on Elasticsearch integration.
Integrating Elasticsearch into Shopware 6:
#.env
...
SHOPWARE_ES_HOSTS="elasticsearchhostname:9200"
SHOPWARE_ES_ENABLED="1"
SHOPWARE_ES_INDEXING_ENABLED="1"
SHOPWARE_ES_INDEX_PREFIX="sw"
SHOPWARE_ES_THROW_EXCEPTION=1
...
Conclusion
Kubernetes offers unbeatable advantages. Anyone who doesn't want to do without zero-downtime deployments, rapid scaling and resilience should consider a transition to Kubernetes. We're happy to advise, train you in Kubernetes, or carry out the transition for you.
Let's talk about your project.
Every project started with a simple conversation. Send us a quick note explaining what it's about. We'll figure out the rest together.
- Marc Achsnich
Team Leadsynaigy
Show email address
Subscribe to the blog now and never miss any news
✔️free of charge ✔️weekly news ✔️expert knowledge
Please accept the corresponding cookies to view this embedded content.
