← All Posts
KubernetesCase StudyAutoscaling

Scaling from a Handful of Customers to Thousands: Breaking Up a LAMP Stack with Kubernetes on GKE

Steve Searcy · Aug 12, 2026 · 6 min read

A client came to us running a platform that had served a modest customer base well for years: a single LAMP stack, a beefy primary MySQL instance, and a deployment process that involved SSHing into a handful of application servers. It worked — until the business started signing on customers by the hundreds, and then the thousands, and every part of that architecture that had been a reasonable simplification became a scaling ceiling instead.

Where the LAMP stack stopped working

The failure modes weren’t subtle. Request handling and account management shared the same PHP application and the same database as billing and reporting, so a slow reporting query at month-end could visibly degrade the front-end experience for every customer simultaneously. Scaling meant buying a bigger box, not adding more of them, because the application wasn’t built to run more than one meaningful instance against the same session state. And every deployment was all-or-nothing: a bug in the billing module meant redeploying — and re-risking — the entire request-handling path too.

None of that is a criticism of the original build. It was the right architecture for a modest customer base. It was never going to be the right architecture for the growth the business was chasing, and the client knew it — the question was how to get from one to the other without an outage anyone would notice.

Decomposing around the domains that actually scale differently

We didn’t rewrite the platform in one motion. The first step was identifying which parts of the monolith had genuinely different scaling and availability profiles, because that’s what actually justifies a service boundary — not just “this would be cleaner as a separate codebase.” Request handling and account access, for instance, needs to stay responsive around the clock across every region the client operates in. Billing and reporting runs in large batches overnight and can tolerate queuing. Document and records access has stricter durability and audit requirements than either. Those are three different scaling and operational profiles wearing one monolith’s clothing.

We peeled services off the LAMP application one domain at a time, behind an API gateway that let the legacy PHP application and the new services coexist during the transition. Each extracted service got its own datastore rather than continuing to share the original MySQL instance — account and request data moved to a managed Cloud SQL instance sized and tuned for its own access patterns, documents moved to object storage with a managed metadata index, and high-volume event data (notifications, activity events) moved onto a managed Pub/Sub-backed pipeline instead of cron jobs polling the shared database. Externalizing data services this way meant the database that used to be a single point of contention became several right-sized ones, each scaled and secured independently.

Autoscaling for what real demand actually looks like

Traffic isn’t flat, and it isn’t uniform across services. Request handling spikes hard at peak hours across every region the client serves; document retrieval spikes around specific workflows; billing batch jobs run overnight regardless of daytime load. Running everything on GKE let us autoscale each of those independently instead of provisioning the whole platform for its worst-case combined peak.

We used Horizontal Pod Autoscaling on request-driven services, scaling on a combination of CPU and custom metrics tied to queue depth for notification delivery, so a backlog of pending messages triggers scale-out before customers start noticing delays. For the node layer itself, GKE’s cluster autoscaling combined with Vertical Pod Autoscaling recommendations let us right-size resource requests over time as real traffic patterns emerged, rather than guessing at launch and re-tuning manually months later. The billing service, by contrast, scales on a schedule tied to its overnight batch windows — no reason to keep that capacity warm at 2pm.

What changed for the business

The customer count grew from a handful to thousands over the course of the engagement, and the platform scaled out with it without a repeat of the shared-resource contention that used to make month-end billing runs a visible risk to everyday traffic. Deployments became per-service instead of all-or-nothing, which meant a fix to the billing service no longer carried any risk to the request-handling path. And because each data service is now independently scaled and monitored, the platform team can see exactly which domain is under pressure at 8am on a Monday instead of debugging a single shared database that’s slow for reasons that could be coming from anywhere.