Skip navigation

Shopware in the fast lane – Shopware 6 with Kubernetes

Marc AchsnichMarc AchsnichTeam Lead · synaigy

12 min read

3D illustration of cloud computing and software development symbols interconnected, featuring a Kubernetes wheel icon at the center.

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

  1. 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)

  2. Managed Kubernetes can currently be run in France, Germany, England, Australia and Russia.

  3. 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.

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.