Est.

Migrating From ECS to EKS Without Downtime

Build EKS alongside ECS, then shift traffic through a shared load balancer.

· 8 min read
AWS Container Service Decisions · October 29, 2026 · 8 min read · 1,857 words

Migrating from ECS to EKS without downtime is a sequenced engineering operation. The safe way is to build EKS while ECS keeps serving users, rebuild the controls that change, and shift traffic through a shared load balancer. Networking, IAM, autoscaling, and deployment safety must work before EKS takes the full load.

Why teams hit ECS's ceiling before they expect to

ECS works well at small scale, but its limits are harder to work around as workloads grow. CloudWatch-based autoscaling can lag by several minutes, while resource fragmentation leaves compute idle. Secrets rotation requires task restarts, and service-level observability pushes teams toward custom dashboards. The migration decision becomes concrete when those constraints interfere with how the application needs to run.

Autoscaling is often the sharpest pressure point. When workloads are I/O-heavy or event-driven, CPU spikes and P99 latency can rise before scale-out even starts. Adding capacity later still leaves a metric-to-action loop that is too slow to react. Meanwhile, Kubernetes-first tools such as Karpenter, ArgoCD, and External Secrets Operator make the ecosystem gap harder to ignore.

Figma’s migration illustrates a different set of blockers. The team needed capabilities ECS did not provide, including StatefulSets, Helm, and a clean way to run Temporal, and landed on three EKS clusters with Karpenter. Specific requirements can make migration necessary, leaving safe execution as the next question.

Why the migration fails when teams treat it as a cutover event

Treating migration as one deployment concentrates networking, IAM, scaling, and pipeline changes into the moment users become dependent on them. Parallel operation separates those risks. It also gives teams time to understand the source environment, which is straightforward when an infrastructure-as-code tool defines it and much slower when console changes must be reconstructed.

The first translation concerns scope. A Task Definition becomes a Pod template inside a Deployment or Job, while an ECS Service becomes a Deployment plus a Kubernetes Service. A Capacity Provider becomes a Karpenter NodePool paired with an EC2NodeClass. Service Auto Scaling separates into pod scaling through HPA and node scaling through Karpenter.

The supporting services change as well. Task IAM Roles become Pod Identity or IRSA, Cloud Map discovery becomes Kubernetes Service plus CoreDNS, and direct secret injection becomes External Secrets Operator. FireLens becomes a Fluent Bit DaemonSet or OTel collector, while ALB integration moves through the AWS Load Balancer Controller. These mappings give teams a plan, but networking and IAM need a closer look because mistakes there cause production incidents.

Phase one: standing up the EKS cluster without touching production traffic

Build the EKS cluster while ECS serves all production traffic. Configure the VPC CNI for expected pod density, define node groups or Karpenter NodePools, and install the AWS Load Balancer Controller. The cluster stays dark throughout this phase. Its load balancer integration must work before traffic shifting starts so that it stays in place through the first canary.

Start the networking plan with how much address space is available. The VPC CNI assigns one IP per pod from the node’s ENI, so subnet size and ENI limits constrain pod density. Without that planning, IP exhaustion can interrupt the migration. The available approaches include pod subnets of /20 or larger, one per AZ, prefix delegation to let each ENI host more IPs, or custom networking with a secondary CIDR.

For node provisioning, Karpenter is the recommended path. It replaces Capacity Providers that use groups of instances that scale automatically, and it handles a mix of Spot/on-demand capacity with automatic fallback. EKS Auto Mode is also a valid option for teams that want less node management. AWS reduced its GPU management fees in July 2026, including a 60% reduction for P-series and Trainium, improving that option’s economics specifically for GPU-heavy workloads.

Translating task definitions to Kubernetes manifests without losing security posture

Most of the container specification is straightforward to translate, but workload identity needs a careful choice. ECS Task Roles become either IRSA, which uses OIDC federation, or EKS Pod Identity. AWS recommends Pod Identity as the simpler path for new deployments. It sends credentials through an agent on each node and needs no OIDC trust configuration, so it works more like ECS Task Roles.

Keep the isolation that the old configuration enforced. Use one ServiceAccount per workload and one IAM role per ServiceAccount, rather than giving every pod a default account with a catch-all role. With IRSA, the ServiceAccount annotation associates that account with its IAM role, and the workload’s Pod template uses that ServiceAccount. That wiring belongs in the manifest design before deployment.

Replace direct Parameter Store injection and init-container patterns with External Secrets Operator syncing from Secrets Manager or HashiCorp Vault into Kubernetes Secrets. The operator handles rotation automatically, so ECS task restarts are no longer needed for that work. But when refresh intervals are short, they can cause enough API calls to trigger rate limiting. Use the longer one-hour interval rather than five minutes, with a manual sync annotation for urgent rotations.

The ALB dual-stack: how to route traffic to both clusters simultaneously

Keep the existing ALB and give it two destinations. One target group serves ECS and the other serves EKS, and weighted routing sets how much traffic each gets. The ECS target group continues operating unchanged. The AWS Load Balancer Controller’s TargetGroupBinding resource connects EKS pods to an existing target group without recreating the load balancer.

This is the architecture that makes gradual migration possible. Requests reach the shared ALB, then branch toward either the ECS target group or the EKS target group. ECS remains available while EKS proves it can handle real traffic.

Athenahealth’s AR Finance migration took place over five weeks. Week 1 kept all traffic on ECS for deployment validation, Week 2 introduced a small EKS canary, and Week 3 moved to an even split. Week 4 sent most traffic to EKS and checked it against a confidence threshold, before Week 5 finished the cutover. The critical safeguard was the reverse path. An ALB rule weight change could return traffic to ECS with fifteen-second propagation, rather than waiting hours for pod or task redeployment.

Replacing ECS autoscaling with two-layer Kubernetes autoscaling

Configure pod scaling and node scaling as separate controls. HPA or KEDA increases the number of pods, while Karpenter or Cluster Autoscaler supplies nodes on which those pods can run. Both layers have to work under load. Configuring only pod scaling leaves additional pods in Pending status when the cluster has no room to schedule them.

For SQS-driven workloads, a KEDA ScaledObject targets the queue with a messages-per-pod ratio and a stabilization window. That shifts the scaling signal toward queued work rather than waiting for CPU pressure to describe the problem indirectly. However, adding a queue trigger does not remove the need to control how quickly capacity contracts.

Avoid an aggressive 300-second cooldown that causes cluster thrashing. A longer stabilization window stops capacity from changing back so quickly. The pod and node layers should be treated as one capacity system during validation, even though they use separate controls. Otherwise, a successful scaling decision at one layer can conceal the lack of usable capacity at the other.

Rebuilding the deployment pipeline around GitOps before the cutover

Rebuild the deployment pipeline in parallel with cluster setup. The ECS sequence of pushing an image, updating a task definition, and triggering a service update does not map cleanly to Kubernetes’ controller-driven model. Simply transplanting that process limits the benefit of moving. GitOps needs to be in the EKS stack from day one, before production traffic depends on it.

Separate the application repository from the deployment manifest repository. A code push triggers CI to test, build, scan the image with Trivy, push it to ECR, and update the image tag in the GitOps repository. ArgoCD detects that change and syncs the cluster, while the Deployment controller handles zero-downtime rolling updates. CI writes the intended deployment, and ArgoCD applies it. That separation decouples builds from deployments and supports promotion between environments.

Progressive delivery then builds on the routing already present. Argo Rollouts supports canary and blue-green delivery through the ALB’s weighted routing. Automated rollback triggers tied to defined P99 latency thresholds connect deployment decisions to observed performance. Establishing this before cutover makes the new platform operable for the next release as well as capable of hosting the current one.

The final cutover sequence and the rollback gate

Define the rollback gate before the final traffic shift. Set numeric thresholds for P99 latency, error rate, and KEDA queue lag, together with the observation window in which a breach triggers reversal. A general instruction to watch dashboards is not a go/no-go condition, and choosing thresholds during an incident turns rollback into a debate.

In Week 5, use the majority-on-EKS phase as the decision point. Move to full EKS traffic only when the earlier validation and confidence threshold have passed. Keep the ECS rollback destination available through the observation window. Once all traffic reaches EKS, check each named signal against the limit agreed for it.

If any threshold is exceeded within that window, immediately revert the ALB weights to ECS. The fifteen-second propagation time makes that action faster than incident escalation. If the signals stay within their limits, the cutover passes the gate.

Where compliance and observability controls need to be rebuilt, not assumed

EKS introduces a Kubernetes API trust boundary in addition to the AWS account boundary, so account-level safeguards alone are not enough. SOC 2 auditors expect access, monitoring, and change-management controls at both layers. The migration changes the scope of that evidence.

Map each AWS control to its in-cluster counterpart. Logical access needs IAM access entries plus Kubernetes RBAC bound to IAM principals, while encryption at rest needs KMS for etcd plus Kubernetes Secrets encryption. CloudTrail covers AWS API calls, and Kubernetes audit logs must reach CloudWatch or a SIEM. Network segmentation combines security groups with Kubernetes NetworkPolicy.

Observability also needs a new implementation before cutover. ECS CloudWatch metrics do not transfer to EKS, which needs Prometheus for metrics, Grafana for dashboards, and Fluent Bit or an OTel collector for log shipping. Without that stack, the team cannot see what is happening during the most sensitive period after cutover. For teams pursuing SOC 2 during migration, the same control mapping provides the foundation, while automated RBAC bootstrapping, audit log routing, and CVE patching can reduce the engineering overhead.

What a successful migration actually looks like in production

Athenahealth’s AR Finance migration had zero production downtime in Q1 2025, with fifteen-plus microservices and six engineering teams in us-east-1 and us-west-2. It also reduced P95 latency by forty percent through KEDA event-driven autoscaling. The outcome went beyond moving containers between schedulers.

SailPoint applied the approach at a larger organizational scale. Its migration covered over one hundred microservices and standardized on Karpenter and KEDA for autoscaling, with Kustomize and ArgoCD supporting GitOps. That combination joined capacity management to a deployment process built for Kubernetes. A cluster alone would not have supplied either.

Parallel serving creates room to validate, GitOps makes the destination operable before cutover, and a defined rollback gate gives traffic shifting a clear reverse path. Zero downtime is achievable because those decisions precede the final move.

More in AWS Container Service Decisions