Skip to main content
CloudJuly 9, 202610 min read

Why So Many Node Apps Still Run on EC2 and PM2, and When It Is Time for ECS or EKS

A huge share of the web still runs on a handful of EC2 instances with Node.js under PM2 behind a load balancer. It is not a foolish setup, but it quietly becomes a liability: unpatched OS and runtime, snowflake servers, risky manual deploys, and resilience that is more oversold than real. Here is why teams stay on it for years, when it is genuinely fine to leave it, and when it is time to move to ECS or EKS.

The Setup You Have Seen a Hundred Times

There is a particular AWS setup that runs an enormous share of the web, quietly, right now. A handful of EC2 instances, two or three of them, each running a Node.js application kept alive by PM2, sitting behind an Application Load Balancer. Someone set it up years ago, it worked, and it has been left alone ever since. If you have run a growing product on AWS, you have either built this or inherited it.

Drawn out, the architecture looks like this:

                      Internet
                         |
                         v
             +-----------------------+
             |  Application Load     |
             |  Balancer (ALB)       |
             +-----------------------+
              /          |          \
             v           v           v
        +---------+ +---------+ +---------+
        | EC2 #1  | | EC2 #2  | | EC2 #3  |   hand-built, patched by hand
        | Node.js | | Node.js | | Node.js |
        | + PM2   | | + PM2   | | + PM2   |   PM2 keeps the process alive
        |  (OS)   | |  (OS)   | |  (OS)   |   OS and runtime drift over time
        +---------+ +---------+ +---------+

  Deploy: SSH into each box, git pull, npm install, restart PM2, one by one.

The important thing to say up front is that this is not a foolish setup. For a simple application with modest traffic and a team that keeps it patched, it is a perfectly reasonable choice. The problem is not the architecture on day one. The problem is what it becomes on year three, when nobody has touched it, the person who built it has moved on, and it is quietly holding up something the business now depends on.

This piece is about why that setup lingers for years, where the bill actually comes due, when it is genuinely fine to leave it alone, and what the right setup looks like once the application has outgrown it.

Why It Sticks Around for Years

The reasons a team stays on this setup are almost never technical. They are human, and they are worth naming honestly, because they are the real blocker.

The first reason is simply that it works. A running system that serves traffic and does not page anyone has enormous inertia. "If it is not broken" is a powerful argument, right up until it breaks, and by then the person who understood it is usually gone.

The second is a lack of in-house expertise. The engineer who set it up may have been a strong developer who knew just enough infrastructure to get the thing live. Containers, orchestration, and a real deployment pipeline are a different skill set, and the team may simply not have anyone who has done it before. Learning it on a live, revenue-carrying system feels like a bad idea, so it does not happen.

The third is fear of change, which is more rational than it sounds. The current setup is a known quantity. A migration is unknown, it touches production, and the downside of getting it wrong is an outage on the thing that pays the bills. Faced with that, "later" is the safe-feeling choice, and later never comes.

The fourth is that nobody owns it. Reliability and infrastructure modernization are everyone's problem and therefore no one's job. Without a person whose actual responsibility is to look at this and say "this needs to move," it drifts, year after year, until an incident makes the decision for you.

None of these are laziness. They are the ordinary reasons good teams end up running load-bearing infrastructure that no one is comfortable touching.

Where the Bill Comes Due

The setup does not fail because it is old. It fails because the risks compound silently while nothing appears to be wrong.

The clearest one is patching debt. Those EC2 instances run an operating system that needs regular security updates and a Node.js runtime that has a fixed support lifecycle. On a hand-built server that nobody reboots, the OS drifts out of support, the Node version reaches end of life, and known vulnerabilities accumulate with no one watching. A long-lived server that is never rebuilt is a growing pile of unpatched issues by default, not by accident.

The second is that the servers are snowflakes. They were configured by hand, the exact steps live in someone's memory or a half-accurate wiki page, and no two are guaranteed to be identical. When one dies, you cannot simply stamp out a replacement, you have to rebuild it and hope you remembered everything. That is the opposite of what you want from infrastructure.

The third is the deployment itself. On this setup a deploy is often a manual ritual: connect to each box, pull the latest code, install dependencies, and restart PM2, one instance at a time, hoping nothing differs between them. There is no immutable artifact, no clean rollback, and no guarantee that what is running matches what is in the repository. It is slow, it is nerve-wracking, and it does not scale past a couple of servers before it becomes a genuine liability.

The fourth is that the resilience is often oversold. Two or three instances behind a load balancer looks like high availability, and it tends to be presented that way, but PM2 restarting a process on a single box is not the same thing as a system that reschedules work when a whole instance dies. If capacity was guessed rather than measured, you are usually either over-provisioned and paying for idle servers around the clock, or under-provisioned and one traffic spike away from falling over. Both are expensive, one in dollars and one in incidents.

Put together, the setup that looked cheap and simple becomes a system that is expensive to run, risky to deploy, and frightening to change. That is the debt, and it accrues interest whether or not anyone is looking at it.

When EC2 and PM2 Is Actually Fine

Modernization for its own sake is its own mistake, so it is worth being clear about when this setup is the right answer. If you are running a single, simple application, with predictable and modest traffic, a small team that genuinely keeps the OS and runtime patched, and no real need for rapid scaling or frequent deploys, then EC2 with PM2 behind a load balancer is a fine, cheap, understandable choice. Do not move to containers because it is fashionable.

The trigger to move is complexity. Multiple services, frequent deploys, real scaling needs, a growing team, uptime that now matters to the business, or a patching and reproducibility burden you can no longer carry by hand. When those show up, the simple setup stops being the cheap option and starts being the risky one.

The Right Setup for a More Complex Application

Once an application has outgrown a hand-built server, the path is to package it as a container and run it on a managed orchestrator. On AWS that means Amazon ECS or Amazon EKS, and the difference between them matters.

ECS, particularly with Fargate, is the pragmatic default. You hand AWS a container image and a small amount of configuration, and it runs, scales, and reschedules your application without you managing servers or an orchestration control plane at all. For most teams leaving an EC2 and PM2 setup, this is the right first destination. It removes the servers, the manual patching, and the snowflake problem in a single move, with the least new complexity to learn.

EKS is managed Kubernetes, and it is the right choice when the complexity genuinely warrants it: many services, a dedicated platform team, portability across clouds, or the wider Kubernetes ecosystem of tooling. It is more powerful and more portable, and it also carries more operational surface. Jumping straight to EKS when ECS would have done the job is a common and expensive way to trade one kind of complexity for another. Match the tool to the application and, just as importantly, to the team that has to run it.

Between the two, the choice comes down to how much complexity you are actually carrying:

Amazon ECS (Fargate)Amazon EKS
Best forMost apps leaving EC2 and PM2Many services, a platform team, cross-cloud portability
You manageA container image and its task configThe cluster and its Kubernetes objects
Control planeFully managed, effectively invisibleManaged Kubernetes that you run workloads on
Learning curveLowHigh, this is Kubernetes
PortabilityAWS-specificPortable across clouds
Pick it whenYou want the servers and the patching gone with the least new complexityThe complexity genuinely warrants Kubernetes

The ECS version of the same application looks like this. The load balancer stays, the servers disappear:

                      Internet
                         |
                         v
             +-----------------------+
             |  Application Load     |
             |  Balancer (ALB)       |
             +-----------------------+
                         |
                         v
             +-----------------------+
             | ECS Service (Fargate) |   AWS runs and reschedules tasks
             | desired count +       |   no servers for you to patch
             | autoscaling on health |
             +-----------------------+
              /          |          \
             v           v           v
          [ task ]   [ task ]   [ task ]    identical containers, one image
             ^           ^           ^
             |           |           |
        +-------------------------------+
        | Container image (immutable)   |   built once in CI, then rolled out
        +-------------------------------+

  Deploy: CI builds the image, registers a new task definition, and ECS does
  a rolling update with health checks and automatic rollback on failure.

Whichever you choose, the wins are the same, and they are precisely the pain points of the old setup, inverted.

EC2 plus PM2ECS or EKS
ServersHand-built, patched by hand, snowflakesManaged or immutable, rebuilt from images
DeploysManual, SSH in and restart, risky rollbackRolling or blue-green from an immutable image, clean rollback
ScalingGuessed capacity, manual response to failureAutoscaling and self-healing on health checks
PatchingOS and runtime drift silentlyRebuild the image to update the base and the runtime
ReproducibilityLives in someone's memoryDefined in code, stood up on demand

The point is not that containers are magic. It is that the properties you actually want, reproducibility, safe deploys, automatic recovery, and a sane patching story, come built in, instead of being things you have to remember to do by hand on every server.

How to Move Without a Rewrite

The reason this migration feels frightening is the fear of a big-bang cutover, but that is not how it should be done. The application itself usually does not need to change. You containerize it as it is, put a real CI/CD pipeline in front of it so deploys become automatic and repeatable, and run the container on ECS behind the same load balancer you already have. Traffic shifts over gradually, the old instances stay as a fallback until the new setup has proven itself, and only then do they get switched off. From there, if the complexity ever demands it, ECS to EKS is an evolution rather than another leap.

Drawn as a timeline, the move is four unremarkable steps, each one reversible:

   Today              Step 1              Step 2               Step 3
 -----------      --------------      ----------------     ---------------
  EC2 + PM2   ->   Containerize   ->   Run on ECS       ->  Retire the
  behind ALB       the app as-is       behind the SAME      old EC2 boxes
  (untouched)      + CI/CD builds       ALB, shift           once ECS has
                   an image             traffic gradually    proven itself

Done this way, the risk is spread across small, reversible steps instead of concentrated into one terrifying weekend. That is the difference between a migration a team is afraid of and one it can actually carry out.

The Real Blocker Is Confidence, Not Technology

If there is one thing to take from all of this, it is that the setup rarely survives because it is the right technical choice. It survives because moving off it requires expertise the team may not have and confidence it has not been given. The technology to do this well has been mature for years. What is usually missing is someone who has done it before, who can say which parts are safe, which order to do them in, and where the real risks actually are.

That is exactly the gap we are built to close. If you are sitting on an EC2 and PM2 setup that has quietly become load-bearing and you are not sure whether to leave it or move it, our migration and AWS management work starts with an honest assessment of whether it is even worth moving, and if it is, a CI/CD pipeline and a container platform sized to your application rather than to a trend. For the deeper question of who owns reliability once it starts to matter, see our take on SRE versus DevOps.

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.