Most Magento clusters cost more than they need to because every tier gets replicated as if every tier were the bottleneck. Only one of them ever is. Get the shape right and you pay for the capacity you actually use, with rolling deploys and honest capacity numbers on top. Get it wrong and you are paying for three copies of a search cluster nobody queries, plus a shared filesystem that makes the storefront slower than the single box you left behind.
Only one tier actually scales
A Magento request spends nearly all of its wall clock time inside PHP. nginx accepts the connection, hands the request to PHP-FPM over FastCGI, and waits. The thing you scale is therefore PHP-FPM worker processes, and every other component is sized by data volume rather than by traffic.
PHP-FPM's own ceiling is pm.max_children. The PHP manual is direct about what it means, "This option sets the limit on the number of simultaneous requests that will be served." Multiply that by your replica count and the product is the real concurrency of your storefront. Size the pod memory limit around a measured worker rather than a guess, because a pod that gets OOM killed under load costs you more than one that queues.
The web tier behaves differently. nginx serving static files and proxying FastCGI is cheap and close to constant. Two replicas for availability is usually the entire story, and adding a third buys nothing while requests are still queueing on PHP.
Sessions and cache have to leave the pod
Any pod can be replaced at any moment, so nothing a customer depends on can live on the pod's own disk. Magento supports Redis and Valkey for session storage, configured from the CLI rather than by hand-editing app/etc/env.php.
bin/magento setup:config:set --session-save=redis \
--session-save-redis-host=valkey-session \
--session-save-redis-port=6379 \
--session-save-redis-db=2 \
--session-save-redis-log-level=4
Adobe recommends keeping the databases apart, with the default cache on database 0, the page cache on 1 and sessions on 2. The session backend also does locking, and those defaults matter once you run many PHP pods. max_concurrency defaults to 6 processes waiting on a lock for a single session, break_after_frontend to 5 seconds and break_after_adminhtml to 30. A storefront that fires several parallel AJAX calls per page view will meet that concurrency number long before it meets your CPU limit.
Media is the part that decides your architecture
Two directories are the whole problem. pub/static is generated output and pub/media is customer data, and they want opposite treatment.
Static content stops being a problem the moment you stop treating it as runtime state. In production mode Magento does not generate static files on demand, so you run the deploy step during the image build and ship the result inside the image.
# Dockerfile, build stage
RUN bin/magento setup:static-content:deploy -f -j 4 en_US
The -j option enables parallel processing with the given number of jobs, and its default is 0, which means no parallelism at all. The -f option is there because by default the tool refuses to run outside production mode.
pub/media is the one that pushes teams into a shared filesystem they did not want. Magento's remote storage module moves it to object storage instead, and it has been available since 2.4.2.
bin/magento setup:config:set --remote-storage-driver="aws-s3" \
--remote-storage-bucket="my-bucket" \
--remote-storage-region="eu-central-1"
Read the constraints before committing to it. Remote storage and Magento's database media storage cannot both be enabled, and file operations have to go through the Commerce framework rather than raw PHP file functions, which some third party modules do not do.
If remote storage is not an option you are buying a ReadWriteMany volume, which Kubernetes defines as a volume that "can be mounted as read-write by many nodes". That is a real line item, usually the largest one, and it is also the change that most often makes a cluster slower than the server it replaced.
Cron runs on exactly one node, and Kubernetes has to be told
Adobe states the constraint plainly for multi-server installs, "crontab can run on only one node". Magento's own scheduler is a crontab entry wrapped in #~ MAGENTO START and #~ MAGENTO END markers, installed with bin/magento cron:install and removed with bin/magento cron:remove. On Kubernetes you want neither, because the CronJob object is the scheduler.
# k8s/magento-cron.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
name: magento-cron
spec:
schedule: "* * * * *"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 1
failedJobsHistoryLimit: 3
jobTemplate:
spec:
template:
spec:
restartPolicy: Never
containers:
- name: cron
image: registry.example.com/magento:2026.08.25
command: ["php", "bin/magento", "cron:run"]
The concurrencyPolicy: Forbid line is the important one. Kubernetes documents it as skipping the new run when the previous one has not finished, which is what stops a minutely schedule from stacking indexer runs on top of each other. The same page warns that scheduling is only approximate, that two Jobs or none may be created, and that the work itself therefore has to be idempotent.
Message queue consumers are a separate decision. They are driven by the cron_consumers_runner block in app/etc/env.php, where cron_run defaults to true, max_messages defaults to 10000 messages before a consumer terminates, and an empty consumers array runs all of them. Setting cron_run to false and running the consumers as their own Deployment gives you a tier you can scale and restart without touching the storefront.
When one larger server is the honest answer
Kubernetes adds line items a single server does not have. A control plane, a load balancer, object storage or a shared filesystem, and the engineer time to keep all of it patched and upgraded.
Work out your own number rather than trusting anyone's rule of thumb. Take the PHP-FPM concurrency you actually measured at peak, multiply it by the memory a real worker uses on your codebase, and that gives you the node capacity you need. Compare that against your current bill using your provider's own cost report, then add the shared storage and the load balancer, because those two are what surprise people.
The cluster earns its keep when traffic is genuinely spiky, when several stores share the platform, or when a team already runs Kubernetes for everything else. A single store with steady traffic, one region, and nobody on call for the cluster is better served by one well-sized server, a warm standby and a boring deploy script. That is not a lesser answer, it is the cheaper route to the same uptime.
If you want the trade-off analysed rather than assumed, our write-up on whether Magento belongs on Kubernetes at all argues the other side of this question. Once the decision is made and the cluster has to be built and kept alive, that is Kubernetes management, and the storefront tuning that belongs beside it is Magento 2 speed optimization.
Talk to the engineer who will own your stack.
No account managers, no offshore handoff. Senior DevOps, direct. Tell us what you are dealing with and you get a straight answer.
Related Articles
How to Boost Magento 2 Performance in a Few Easy Steps
Magento 2 delivers incredible flexibility for eCommerce, but without proper optimization it can become sluggish. This guide walks through ten proven DevOps strategies to dramatically speed up your store, from PHP upgrades and full-page caching to Varnish, Redis, CDN configuration, and ongoing code audits.
MagentoHow to Upgrade Magento 2 from 2.4.7 to 2.4.8
Keeping Magento current is critical for security, performance, and compatibility. This step-by-step guide walks developers through upgrading from Magento 2.4.7 to 2.4.8, covering system requirements, pre-upgrade checks, Git workflow, Composer commands, and post-upgrade validation.
MagentoHow to Completely Disable "Compare Products" in Magento 2
Magento's built-in Compare Products feature can add unnecessary clutter and slow down page loads. This guide shows you how to fully remove it using layout XML overrides, CSS rules, and a quick CLI deploy -- keeping your storefront clean and fast.