Why I Moved My SSR Frontend from Kubernetes to AWS Amplify
Escaping K8s complexity, shrinking the blast radius, and making the UI faster than ever.
When I first started working with our infrastructure, having the frontend inside Kubernetes didn’t seem like a bad decision.
We already had an EKS cluster. Our backend services were running there. We had ingress, load balancing, CI/CD, monitoring, Docker images — all the usual pieces.
So why not put the frontend there too?
At least initially, it made perfect sense.
Our frontend was packaged as a Docker image, deployed as a Kubernetes workload, exposed through our ingress, and scaled using the same infrastructure as everything else.
It worked.
And that’s what made the problem difficult to notice.
There wasn’t some massive outage that forced us to move it. There wasn’t a catastrophic scaling incident.
Instead, we gradually started asking ourselves:
Why are we using Kubernetes to solve this problem?
The more I looked at it, the more unnecessary it felt.
Our backend genuinely benefited from Kubernetes. We had multiple services, different resource requirements, rolling deployments, internal communication, workers, and workloads that needed to run continuously.
The frontend was different.
It was essentially a web application that needed two things:
- Serve static assets extremely well.
- Run server-side rendering when a request actually required it.
We were maintaining an entire Kubernetes workload to accomplish that.
Eventually, I decided to move the SSR frontend out of Kubernetes and into AWS Amplify.
And honestly, the biggest lesson wasn’t about Amplify.
It was about putting each workload on infrastructure that matches what it actually needs.
The Frontend Wasn’t the Problem. The Infrastructure Around It Was.
One thing I’ve learned working with infrastructure is that “it works” isn’t the same as “it’s the right architecture.”
Our frontend running in Kubernetes wasn’t inherently wrong.
We had:
User
↓
CloudFront
↓
Load Balancer
↓
Ingress
↓
Kubernetes Service
↓
Frontend Pod
↓
Node.js SSROn paper, perfectly reasonable.
But look at everything involved just to render a web page.
We had to think about:
- Docker images
- Kubernetes Deployments
- Services
- Ingress rules
- Resource requests and limits
- Pod replicas
- Node capacity
- Rolling deployments
- Cluster autoscaling
- CI/CD configuration
- Health checks
- Logs and monitoring
None of these things are bad.
We already needed most of them for our backend.
The problem was that the frontend became another workload competing for the same infrastructure.
And that’s when the concept of blast radius started becoming important to me.
The Blast Radius Was Bigger Than It Needed to Be
Imagine our frontend suddenly gets a huge amount of traffic.
Maybe a marketing campaign goes live.
Maybe someone posts a link to the application.
Maybe one page suddenly becomes popular.
If the frontend is running in Kubernetes, we need to scale it.
More pods.
More CPU.
More memory.
Potentially more nodes.
And now something that started as a frontend traffic problem has become a Kubernetes capacity problem.
That’s where things get interesting.
Our backend APIs are running on the same cluster.
So theoretically:
Frontend traffic spike
↓
More frontend pods
↓
More CPU / Memory consumption
↓
Kubernetes cluster pressure
↓
Potential impact on backend workloadsOf course, good resource requests, limits, autoscaling and node separation can reduce this risk.
But that’s exactly the point.
We were adding more infrastructure controls to prevent one workload from interfering with another workload.
At some point I asked myself:
Why not just separate them?
Not logically.
Physically.
So Why Not Just Put the Frontend in S3?
This was the obvious question.
If we’re moving the frontend away from Kubernetes, why not simply build it and upload the output to S3?
For a purely static website, that’s exactly what I would do.
But our application uses SSR.
That changes things.
With a static application, the deployment can essentially become:
Build
↓
HTML / CSS / JS
↓
S3
↓
CloudFrontThere is no server involved when someone requests the page.
But with SSR, some requests require application code to execute before the response is generated.
Conceptually:
User
↓
CloudFront
↓
SSR runtime
↓
Application executes
↓
HTML generated
↓
UserSo S3 alone wasn’t enough.
We needed somewhere to execute the server-side code.
That’s where Amplify became interesting.
What Actually Changed?
The biggest change wasn’t really the frontend code.
It was the operational model.
Instead of thinking:
“How do I run this Node.js application?”
we started thinking:
“What parts of this application actually need compute?”
That distinction matters.
The frontend build produces different kinds of artifacts.
Some of them don’t need a server at all:
- JavaScript bundles
- CSS
- Images
- Fonts
- Other static resources
Those can be distributed through a CDN.
Other parts actually require server-side execution.
Those need compute.
Amplify handles that separation for us.
At a high level, the architecture becomes something like:
┌── Static Assets
│ ↓
User → CloudFront ───────┤ S3 / CDN
│
└── Dynamic Request
↓
SSR
↓
Managed ComputeThe important part for me wasn’t that Amplify magically made the application faster.
It was that I stopped having to manage the infrastructure required to make this happen.
What Deployment Looks Like Now
This was probably the part I appreciated the most.
Previously, deploying the frontend was a proper infrastructure deployment.
Get Adhikarialan’s stories in your inbox
Join Medium for free to get updates from this writer.
A typical flow looked something like:
Developer pushes code
↓
CI pipeline
↓
Build Docker image
↓
Push image to registry
↓
Update Kubernetes deployment
↓
Kubernetes pulls image
↓
New pod starts
↓
Health checks
↓
Traffic shiftsAgain, nothing unusual.
But there’s a lot of machinery involved.
With Amplify, the workflow became much closer to what I actually wanted from a frontend deployment:
Developer pushes code
↓
Amplify detects change
↓
Build
↓
Deploy
↓
DoneThat reduction sounds small when written down.
It isn’t.
When you’re responsible for infrastructure, every additional moving part eventually becomes something you have to monitor, troubleshoot, secure, upgrade or explain to someone else.
The Part I Didn’t Fully Appreciate Before: Separation of Failure Domains
This was probably the strongest argument for the migration.
Before:
Kubernetes Cluster
┌─────────────────────────────┐
│ │
│ Frontend │
│ │
│ Backend API │
│ │
│ Workers │
│ │
│ Other Services │
│ │
└─────────────────────────────┘After:
Frontend
│
▼
AWS Amplify / CDN
│
│ API calls
▼
Kubernetes
│
┌─────┴─────┐
│ │
Backend WorkersNow the frontend and backend don’t share the same compute resources.
If the frontend suddenly needs more capacity, it doesn’t mean I need to worry about whether the backend has enough CPU.
If the Kubernetes cluster has a problem, the frontend isn’t automatically part of that failure domain.
And if the frontend deployment fails, I don’t have to worry about accidentally affecting backend workloads.
That’s a much cleaner boundary.
But Did Amplify Make Everything Better?
No.
And I think this is where a lot of articles about managed services become misleading.
Moving something to a managed platform doesn’t remove complexity.
It moves the complexity somewhere else.
With Kubernetes, I have a lot of control.
I can decide exactly how the container runs, how networking works, how resources are allocated, how deployments happen, and how the application interacts with the cluster.
With Amplify, I give up some of that control in exchange for simplicity.
That’s a trade-off.
And it’s an important one.
If I needed:
- Custom container behavior
- Long-running processes
- Complex networking requirements
- Specialized Node.js runtime behavior
- Very specific infrastructure controls
- Tight integration with other Kubernetes workloads
I’d probably keep the frontend in Kubernetes.
The point isn’t that Amplify is better than Kubernetes.
The point is that Kubernetes wasn’t necessarily the best tool for this particular workload.
What About Performance?
This is another area where I think it’s easy to oversell the architecture.
Moving from Kubernetes to Amplify doesn’t automatically make every application faster.
Performance depends on the application, caching strategy, SSR workload, JavaScript bundle size, API latency, database performance, and a dozen other things.
But the architecture gave us a better starting point.
Static content can be served from the CDN instead of travelling all the way through:
CloudFront
↓
Load Balancer
↓
Ingress
↓
Kubernetes Service
↓
Frontend PodFor static content, that’s unnecessary.
If the browser needs a JavaScript bundle or an image, I don’t need a Kubernetes pod involved in that request.
The closer I can serve static content to the user, the better.
And for dynamic pages, I only need compute when I actually need compute.
That separation makes architectural sense.
The Biggest Change Was Actually My Mindset
Before this migration, I think I had a very infrastructure-oriented way of looking at the problem.
We already had Kubernetes.
So my first instinct was:
“How do we run this on Kubernetes?”
After the migration, I started asking a different question:
“Does this need Kubernetes at all?”
That’s a much more useful question.
As engineers, especially when we start working with Kubernetes, it’s easy to fall into the trap of putting everything inside the cluster.
We have Kubernetes.
We know Kubernetes.
We know how to deploy applications there.
So naturally, every new workload becomes another Deployment, another Service, another Ingress.
But infrastructure shouldn’t be chosen because we already have it.
It should be chosen because the workload benefits from it.
Kubernetes Still Makes Sense for Our Backend
This migration didn’t make me anti-Kubernetes.
Quite the opposite.
It actually made me more confident about where Kubernetes belongs in our architecture.
Our backend has characteristics that make Kubernetes useful:
- Multiple microservices
- Independent deployments
- Different resource requirements
- Internal service-to-service communication
- Background workers
- Horizontal scaling
- Centralized observability
- Infrastructure that we want to control
That’s a good Kubernetes workload.
The frontend was different.
It didn’t need to share that environment just because it was part of the same application.
The Architecture I Prefer Now
At a high level, our architecture became:
Internet
│
▼
┌─────────────────┐
│ AWS Amplify │
│ / CDN │
└────────┬────────┘
│
┌────────┴────────┐
│ │
Static SSR
Assets Compute
│ │
▼ ▼
Cached Server-side
Content Rendering
│
│ API calls
▼
┌──────────────┐
│ Kubernetes │
│ / EKS │
└──────┬───────┘
│
┌───────────┼───────────┐
▼ ▼ ▼
API 1 API 2 API 3The frontend is responsible for being a frontend.
Kubernetes is responsible for running backend workloads.
And the boundary between the two is mostly HTTP/API communication.
That feels much more natural to me.
The Lesson I Took Away
The biggest lesson from this migration wasn’t:
“AWS Amplify is better than Kubernetes.”
It wasn’t.
It was:
Don’t make your infrastructure more complicated than your workload requires.
Kubernetes is incredibly powerful.
But that power comes with operational responsibility.
If you’re already running a large Kubernetes platform, putting an SSR application there may be completely reasonable.
But if you’re managing Kubernetes largely because you need somewhere to run one frontend application, I’d seriously question whether you’re solving the right problem.
For us, moving the frontend out of Kubernetes meant:
- Less infrastructure to maintain
- A smaller failure domain
- Simpler frontend deployments
- Independent scaling
- Better use of CDN capabilities
- Less coupling between frontend and backend infrastructure
And perhaps most importantly, it gave the infrastructure team one less thing to worry about.
Sometimes the best infrastructure decision isn’t adding another layer.
It’s removing one.








