
The website ran on about twenty virtual machines, every one of them on an operating system past end of life. Each environment had five or six of them: author, publish, dispatcher, a redirect server. Production was a set of single machines on local disks. And the CDN in front of it all was fetching every request from the origin, because caching was switched off.
In October and November 2025 I moved it to one AKS cluster. Here's what that involved, and what I learned about putting a stateful, opinionated CMS like Adobe Experience Manager into containers.
The first option on the table was the obvious one: rebuild the same VMs on a supported OS and carry on. When I estimated both paths, the effort for a like-for-like refresh and for a move to Kubernetes came out close.
That settled it. A refresh would have fixed the OS and nothing else. Every production role would still be one machine on one disk. Deploys would still be manual runs. For roughly the same effort, the Kubernetes move fixed the reason the platform was fragile, not just the date on its OS.
AEM is not a stateless web app. The author and publish instances keep their repository on disk, and they expect it to be there after a restart.
So they ran as StatefulSets, each with its own Azure managed disk as a persistent volume. One author. Two publishers, so one can restart without taking publishing down. Dispatchers in front, with a 50 GiB cache volume each. The dispatcher is AEM's Apache-based caching layer, and it behaves well in containers because its cache can be rebuilt.
Container images for author, publish and dispatcher were built from scratch, with Helm charts per tier. Terraform defined the cluster, the network and the container registry. Traffic came in through Azure Application Gateway, with cert-manager for certificates.
Instead of a set of VMs per environment, one cluster served all of them through namespaces, with separate node pools:
The production pool kept one node more than the workload needed. If a node or a zone had trouble, the pods had somewhere to go without anyone being paged. That spare node costs money every month. I'd make the same call again: the alternative was the old setup, where losing one VM meant losing that role.
Deploys had been manual Jenkins and Ansible runs. They moved to a pipeline with an atomic production step: the author and both publishers are updated as one release, so you never end up with publishers on different versions serving different content.
This was the cheapest fix in the project. The CDN had been in front of the site the whole time, but with caching off it was a pass-through. Every visitor request went to the origin VMs.
When the CDN was pointed at the new origin, caching was switched on. If you have a CDN in front of anything, check its hit ratio today. A CDN with a hit ratio near zero is a very expensive proxy.
After the cutover, the old QA and UAT VMs and the non-production cluster nodes sat around. In March 2026 I powered them off for a week. Nothing broke, so I deleted all 14. The estimated cluster cost dropped from about $1,796 to about $1,376 a month.
"Power off for a week, then delete" is my default for anything I'm not sure is unused. It's slower than deleting straight away, but the week of silence is the evidence.
Some months later the cluster's node count was questioned: why did a cluster that had shown 3 nodes now show 8? It hadn't grown. It had always run 8: 2 system nodes, 5 for AEM in production and 1 spare. The lower number had been seen during a managed upgrade, when Azure rotates nodes out and in.
The lesson for me: write the expected node count, and the reason for each node, into the handover docs. People look at the portal, not at your design.
The platform carried production from the November cutover through the 2025 holiday peak. In early 2026 the site moved off AEM entirely and the cluster was wound down. That's normal. Platforms exist to serve the business, and the business changed direction. The work bought a supported, zone-spread platform for the busiest season of the year instead of end-of-life VMs.
Save a before measurement of page speed. Everyone agreed the site got faster, but I didn't keep a saved report from before the move. Without one you end up with several different "before" numbers in people's memories. Run a page test and export it before you touch anything.
Put the CDN fix first. Switching caching on didn't need the migration. Doing it on day one would have made the old site faster while the new one was being built.
Size the spare capacity in writing. See the node count story above.
If you're sitting on a legacy CMS on ageing VMs, I can help you plan the way off them. See Kubernetes & Platform.
A Sunday control-plane move failed at 14:44 when a script wiped its own snapshot. A rollback step put things back, and the retry finished in about 66 minutes.
Spot nodes were reclaimed, the on-demand fallback hit its own limit, and production was down for about 25 minutes. Then I moved production off spot.
A production rollout sat blocked for 8 days while the site looked healthy. The first week of real alerts found it, and stuck queue messages too.