Skip to content
AKNOSTIC
← Back to case studies
Digital InfrastructureIDP Engineering

We built a Kubernetes operator for Magento

mage-operator (Aknostic open source, Apache 2.0)

// KEY OUTCOMES

  • ✓One MageStore resource replaces 16 hand-assembled decisions
  • ✓Composes four existing operators, each with an external opt-out
  • ✓Upgrades run once, as a Job, behind a maintenance fence
  • ✓15 releases in 13 days, every load-bearing decision in an ADR

Running Magento on Kubernetes for one shop is a solved problem: you write a Helm chart with your assumptions baked in. Running it for many shops, or across dev, staging, and production on the same code path, is not. Every team ends up making the same decisions about database provisioning, Redis layout, consumer Deployments, cron groups, Varnish VCL, multi-store dispatch, upgrade orchestration, and public-edge TLS, and each makes them slightly differently.

We built a Kubernetes operator so those decisions live in one place. mage-operator turns a Magento, Adobe Commerce, or Mage-OS store into a single declarative resource. You bring the container image, and the operator owns the Kubernetes shape.

Not affiliated with, endorsed by, or sponsored by Adobe Inc. "Magento" and "Adobe Commerce" are trademarks of Adobe Inc.

Open source on GitHub

mage-operator is Apache 2.0 and the repository is public. It isn't ready for production yet, but on a kind cluster a store reaches Ready in about five minutes. The install steps are at the end of this post.

Solved for one shop, unsolved for many

The top-ranked guide to Magento 2 on Kubernetes walks through everything you set up by hand (PHP-FPM and NGINX pod patterns, database, Elasticsearch, two Redis roles, Varnish, cron with fork-disabling environment variables, init-container migrations, autoscalers, ingress) and then says, honestly: "we wouldn't recommend you run it on production as-is." PHOENIX-MEDIA's Helm chart, the most credible packaged option, is battle tested in production. It still leaves domains, VCL customization, secrets, storage classes, and SMTP to you, and it doesn't address multi-store. The most comprehensive free guide covers image building, zero-downtime deploys, HA, monitoring, and security across dozens of chapters, and never mentions an operator, because until now there wasn't one to mention.

Each of those is a reasonable answer for one shop. Run five shops, or one shop across three environments, and the duplicated decisions start to drift apart. This is the golden-path argument applied to commerce: a golden path only helps when every shop and environment follows the same one.

One resource in practice

A minimal MageStore is about fifteen lines:

apiVersion: mage.aknostic.com/v1alpha1
kind: MageStore
metadata:
  name: store
spec:
  image:
    repository: registry.example.com/myshop/magento
    tag: "2.4.8"
  workloads:
    frontend:
      replicas: 1
  bootstrap:
    admin:
      email: admin@example.com

Apply it, wait for the Ready condition, and read the operator-generated admin password from a Secret. The full spec covers what the guides have you assemble by hand:

You used to decideMageStore spec group
Database provisioning, users, backupsdatabase (composed MariaDB, or external)
Search clustersearch (composed OpenSearch, or external)
Cache & sessions layoutcache (managed Valkey, or external)
Message queuequeue (composed RabbitMQ, or external)
Frontend scalingworkloads
Queue consumers as Deploymentsconsumers
Cron groupscron
Media storage (S3)media
Varnish + VCLvarnish
Transactional mailsmtp
Hosts, TLS, public edgerouting (Envoy Gateway)
Multi-store dispatchstores
First install & adminbootstrap
Version migrationsupgrade
NetworkPolicies, workload security contextssecurity (v0.7.0 arc, in progress)
Admin URL pathbootstrap.admin.backendFrontname

Two contracts keep it portable

First, the image contract. The operator never builds or modifies your Magento image. You bring one that honors a documented interface, and if you want to try the operator before building your own, the repo includes a reference test image that satisfies it.

Second, configuration through environment variables only. The operator does not generate env.php. It emits a Secret of individual environment variables (MAGENTO_DATABASE_HOST, MAGENTO_REDIS_HOST, and so on), and your env.php reads them with getenv(). We considered an ECE-Tools emission mode and dropped it. ECE-Tools is an Adobe Commerce Cloud construct, and this operator is for traditional-hosting shops on Hetzner, Scaleway, bare metal, or self-hosted Kubernetes.

Composing existing operators

The operator stays small by leaving solved problems to the projects that already solve them. mariadb-operator, opensearch-k8s-operator, and rabbitmq-cluster-operator handle databases, search, and queues, and Envoy Gateway handles the public edge. What's left for mage-operator is the Magento-specific glue: wiring those systems into one store, watching one resource, and reporting its status conditions.

Composition is the default, and every composed dependency has an external escape hatch. Point spec.database.external at your managed MySQL and the operator skips the composed database entirely.

The controller design follows the Kubebuilder good practices: idempotent reconciliation, and status conditions per the Kubernetes API conventions. Those practices say little about when to depend on someone else's operator and when to stop.

Where composition stops: CROSSSLOT

The first cache implementation composed a Valkey operator, the same way as MariaDB and OpenSearch. We reverted it, because with that operator in place setup:upgrade fails during cache-tag cleanup:

CROSSSLOT Keys in request don't hash to the same slot

ADR 009 traces the root cause. The upstream Valkey operator runs every deployment with cluster-enabled yes regardless of size, so even a single instance is in cluster mode. Magento's Redis cache backend sends multi-key DEL operations during cache-tag cleanup, and in cluster mode those fail unless every key hashes to the same slot. Magento's keys don't, and short of rewriting the cache backend there is no fix on the Magento side. Adobe has an open issue for it (#39224).

So the cache is a bare StatefulSet that the operator manages itself, with Sentinel for HA. It looks less elegant on the architecture diagram, but it matches Magento's cache semantics. Keeping the Valkey operator would have made every future cache bug depend on someone else's cluster topology.

Upgrades behind a fence

Everything above happens at install time, which is where the existing guides stop. The operator differs from them most in what happens when you upgrade.

Magento ships schema and data migrations with every release, and setup:upgrade is not transactional. ADR 008 spells out what that means: the command iterates over every module's schema and data patches and commits each one as it runs. "If the seventh patch of twenty-three fails, patches one through six are already in the database. There is no rollback."

So changing spec.image.tag starts a sequence:

fence up
upgrade Job
cache flush
workloads roll
fence down, traffic returns
  1. The fence goes up. A Service-selector flip takes the storefront out of traffic, and the operator holds consumers and cron so nothing writes into a half-migrated database.
  2. setup:upgrade runs once, as a Job, against the new image.
  3. The operator flushes the cache, then rolls the workloads to the new tag.
  4. The fence comes down. Traffic returns only when the schema and the code agree.

The existing paths don't model this boundary. The init-container pattern runs migrations on every pod start while traffic is live, and helm upgrade --wait confirms the rollout is ready without modelling Magento's schema migration at all.

How it was built: a two-hour calibration and 15 releases in 13 days

The build process left its artifacts in the repo, starting with a timed experiment. TIMING.md records a two-hour calibration session on 22 April 2026, from 04:52 to 06:40, with a budget table and actuals per slot. Its stated goal was to find out "what is underspecified, what the plan got right, and where the estimate will hold or break". v0.1.0 shipped three days later, and v0.6.4 thirteen days after that: fifteen releases, each with a changelog entry. Upgrade orchestration landed in v0.2.0 and the cache topology change in v0.3.0, and a sixteenth release arc, the v0.7.0 security baseline, is already underway.

Every load-bearing decision got an ADR. There are thirteen so far (ADR 001 is reserved and, in the repo's own words, "not yet written"), covering config emission modes, bootstrap as a Job, upgrade orchestration, cache topology, TLS at the public edge, base-URL derivation, and the security baseline. Development ran as planned sessions in Claude Code, with the repo carrying its own working context in CLAUDE.md, per-session plans, and per-session changelog entries. Claude Code assisted with the engineering, and the artifacts record the discipline around it: timed calibration, ADRs, and semver from day one.

We used the same ship-early, iterate-in-public approach for our GoAlert provisioning operator. Releases run through our own GitLab CI, which builds distroless images signed with cosign, uses SOPS-encrypted secrets, and runs end-to-end tests on a kind cluster, including upgrade and cache scenarios.

Where it stands

Fifteen releases in, the repo is candid about its limits. The changelog puts the current release at v0.6.4, with the v0.7.0 security-baseline arc in progress. ADR 014 defines its shape, a spec.security group has landed, and admin-URL randomization already works end to end. New stores get a random 16-character admin path stored in a Secret. Existing stores keep /admin/ on purpose, because breaking bookmarks in a patch release would violate the project's upgrade-safety rule. The README's status section is several versions behind the changelog.

Do not use this in production yet

The README warns against production use, and that warning still holds. Validation on real infrastructure (EKS and Scaleway Kapsule) runs in parallel with the releases.

Try it yourself

The operator is Apache 2.0 and the repository is public on GitHub. Install the dependency operators, install the mage-operator Helm chart from the OCI registry, apply a minimal MageStore pointing at an image that honors the image contract, and run kubectl wait --for=condition=Ready. On a kind cluster that takes about five minutes. You don't need to build your own image first: the bundled reference image works, though its first build takes around 30 minutes. If something breaks, file an issue in the repo.

If what you want is a commerce platform on Kubernetes that your team runs with confidence, across environments and through upgrades, that is the platform engineering work we do, and this operator is our version of it, in public.

Facing similar challenges?

Let's talk