Running Containers on AWS: ECS, EKS, and Fargate
Run containers on AWS without drowning in options: ECR for images, ECS vs EKS for orchestration, EC2 vs Fargate for compute, and which combo fits your team.
The first time you try to run a container on AWS, the choices feel like a maze: ECR, ECS, EKS, Fargate, App Runner, plain EC2 with Docker. They overlap, the names give little away, and every blog post recommends a different one. The good news is that the landscape sorts into three independent questions, and once you separate them the decision gets simple. Where does the image live? What orchestrates the containers? What machines actually run them? This guide answers those three questions, shows how the pieces connect through networking and IAM, and gives you a clear rule for when each option is the right call. It is decision-oriented, not a step-by-step tutorial - the goal is to leave you able to pick the right stack and know why.
The three questions that define the landscape
Every container setup on AWS is an answer to three separate questions. Keep them separate and the maze collapses into a short decision.
- Registry: where do the images live? On AWS this is almost always ECR (Elastic Container Registry). It is the one part of the stack with no real debate.
- Orchestrator: what schedules and manages the containers? This is the ECS vs EKS decision. ECS is the AWS-native orchestrator; EKS is managed Kubernetes. This is the big architectural fork.
- Compute: what machines run the containers? This is the EC2 vs Fargate decision. EC2 means you manage a pool of servers; Fargate means AWS runs the containers with no servers for you to see. This choice is largely independent of the orchestrator - both ECS and EKS can run on either.
The mistake is treating these as one big choice. They are three dials. "ECS on Fargate" and "EKS on managed node groups" are just two settings of the same three dials. Decide each on its own merits.
ECR: the image registry
Before anything runs, your image has to live somewhere the orchestrator can pull it. ECR is AWS's private Docker registry - the equivalent of Docker Hub, but inside your account, with IAM controlling who can push and pull.
You create a repository per image (one for web, one for worker, and so on), then push tagged images to it. Authentication is not a stored password; you exchange your IAM credentials for a short-lived token.
# Create a repo
aws ecr create-repository --repository-name web
# Log Docker into ECR (token is valid ~12 hours)
aws ecr get-login-password --region us-east-1 \
| docker login --username AWS --password-stdin \
123456789012.dkr.ecr.us-east-1.amazonaws.com
# Tag and push
docker tag web:latest 123456789012.dkr.ecr.us-east-1.amazonaws.com/web:1.4.0
docker push 123456789012.dkr.ecr.us-east-1.amazonaws.com/web:1.4.0
Two things worth knowing early. First, ECR integrates with IAM for access and can scan images for known vulnerabilities on push - turn that on, it is nearly free insurance. Second, because ECR lives in your account and region, pulls from ECS or EKS in the same region are fast and do not leave AWS's network, which also keeps data-transfer costs down. Whatever orchestrator you pick, ECR is where the images go.
ECS: the AWS-native orchestrator
ECS (Elastic Container Service) is AWS's own container orchestrator. It does what Kubernetes does - keep the right containers running, restart failures, roll out new versions, load-balance traffic - but with a much smaller surface area and deep integration into the rest of AWS. There is no control plane for you to run or pay for; ECS is a free AWS-managed service, and you pay only for the compute underneath.
ECS has three core concepts. Learn these and you understand ECS.
- Task definition - the blueprint. A JSON document describing one or more containers that run together: which image, how much CPU and memory, ports, environment variables, log configuration, and the IAM roles to use. It is roughly the ECS analog of a Kubernetes pod spec.
- Task - a running instance of a task definition. One task is one running copy of your containers. Tasks are disposable; if one dies, ECS can start a fresh one.
- Service - the controller that keeps N tasks running from a task definition, replaces failures, registers tasks with a load balancer, and manages rolling deployments. This is the ECS equivalent of a Kubernetes Deployment plus Service.
A task definition looks like this (trimmed):
{
"family": "web",
"networkMode": "awsvpc",
"cpu": "256",
"memory": "512",
"executionRoleArn": "arn:aws:iam::123456789012:role/ecsTaskExecutionRole",
"taskRoleArn": "arn:aws:iam::123456789012:role/webTaskRole",
"containerDefinitions": [
{
"name": "web",
"image": "123456789012.dkr.ecr.us-east-1.amazonaws.com/web:1.4.0",
"portMappings": [{ "containerPort": 8080 }],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/web",
"awslogs-region": "us-east-1",
"awslogs-stream-prefix": "web"
}
}
}
]
}
You register that, then create a Service that says "run 3 of these behind this load balancer." ECS reconciles: it launches tasks, wires them to an Application Load Balancer target group, watches health, and on a new task-definition revision it does a rolling update the same way a Deployment does. If you already understand Kubernetes Deployments and Services, ECS is a smaller version of the same ideas with AWS-native names. See the Kubernetes fundamentals guide for that mental model - the desired-state control loop is identical.
EC2 vs Fargate: the compute question
ECS tasks have to run on actual compute, and there are two launch types. This is the second dial, and it is where "serverless containers" comes from.
EC2 launch type - you run a cluster of EC2 instances (with the ECS agent on them) and ECS packs tasks onto them like a scheduler filling machines. You own those instances: you choose the instance types, patch the OS, handle scaling the instance pool, and pay for them whether or not they are full. The upside is control and cost efficiency at scale - you can pick exactly the right instance types, use Spot instances, use GPUs, and bin-pack many small tasks onto a few large boxes so you are not paying for idle overhead per task.
Fargate launch type - serverless. You do not create or see any instances. You hand ECS a task definition with a CPU/memory size, and AWS finds capacity, runs your task in an isolated micro-VM, and bills you per task for the vCPU and memory it uses, per second. No servers to patch, scale, or secure at the OS level. The tradeoffs: you pay a premium per unit of compute compared to well-utilized EC2, you cannot bin-pack multiple tasks onto shared hardware (each task gets its own isolated capacity), and some low-level options (GPUs on ECS, certain kernel features, host access) are limited or unavailable.
The rule of thumb:
- Reach for Fargate by default. For most services, not managing servers is worth the premium. No patching, no capacity planning, no half-empty instances - you describe the task and it runs. Small teams and variable or spiky workloads especially benefit.
- Move to EC2 when the economics or requirements demand it. Large, steady-state fleets where a few cents of per-task premium adds up; workloads that need GPUs, specific instance types, or host-level access; or heavy bin-packing of many tiny tasks. At that scale the operational cost of managing instances is worth the savings.
Fargate is not a different orchestrator - it is a compute mode. The same ECS Service, task definition, and load balancer work either way; you are only changing what the tasks land on.
EKS: managed Kubernetes
EKS (Elastic Kubernetes Service) is AWS running the Kubernetes control plane for you. You get a real, conformant Kubernetes cluster - the same API, the same kubectl, the same YAML, the same ecosystem of Helm charts and operators - without having to run and upgrade the control plane (the API server, etcd, and controllers) yourself. AWS manages that part and charges a flat hourly fee per cluster; you bring the worker nodes and pay for those.
Why choose EKS over the simpler ECS? Two reasons, and they are the whole case:
- You want the Kubernetes ecosystem. Helm, ArgoCD, Istio, Prometheus operators, cert-manager, and thousands of tools that assume Kubernetes. If your team already knows Kubernetes or needs those tools, EKS lets you use them as-is.
- You want portability. Kubernetes manifests run on any Kubernetes - EKS, GKE, on-prem, a laptop. If avoiding lock-in to one cloud's proprietary orchestrator matters, standard Kubernetes buys you that (though in practice you will still lean on AWS load balancers, IAM, and storage, so portability is real but not free).
Like ECS, EKS has two ways to provide compute for its pods:
- Managed node groups - EKS provisions and manages a group of EC2 instances as Kubernetes worker nodes, handling the provisioning, updates, and draining for graceful upgrades. You still choose instance types and sizes; AWS handles the lifecycle. This is the common default. Karpenter, an open-source autoscaler AWS backs, is often added on top to provision right-sized nodes just in time.
- Fargate - the same serverless compute, now backing Kubernetes pods. You define Fargate profiles that say which pods (by namespace/label) run on Fargate, and those pods get an isolated micro-VM each with no node for you to manage. Good for spiky or per-pod-isolated workloads, with the same premium-and-limitations tradeoff as ECS on Fargate.
The honest cost of EKS is not the control plane fee - it is everything around it. Even though AWS runs the control plane, you still run the cluster: node upgrades, add-ons (the CNI, CoreDNS, ingress controllers), RBAC, and staying current with Kubernetes version churn. EKS gives you Kubernetes; it does not give you a team that already knows Kubernetes.
The core tradeoff: ECS vs EKS vs Fargate
Now the map. ECS vs EKS is a genuine fork; Fargate is a compute mode you can apply to either.
- ECS is simpler and AWS-integrated. Fewer concepts, no Kubernetes version treadmill, no control-plane fee, and tight, low-config wiring into ALBs, IAM, CloudWatch, and the rest of AWS. The cost is that it is AWS-only and its ecosystem is much smaller than Kubernetes's.
- EKS is standard Kubernetes but more to run. You get the enormous Kubernetes ecosystem and cloud portability, at the price of more moving parts, real Kubernetes operational skill, and ongoing upkeep.
- Fargate removes node management for either. Whichever orchestrator you pick, Fargate takes servers off your plate at a compute premium and with some low-level limits. EC2 gives you control and better economics at scale, in exchange for owning the instances.
A decision that works for most teams:
- Small team, no strong Kubernetes need, want to ship: ECS on Fargate. The least to operate, and AWS-native by default. This is the right call more often than people admit.
- You already run Kubernetes, or need its ecosystem/portability: EKS. Use managed node groups (add Karpenter for autoscaling), and Fargate for workloads that want per-pod isolation.
- Large, steady, cost-sensitive fleet, or special hardware: either orchestrator on EC2 launch type / node groups, where bin-packing and reserved or Spot capacity pay off.
- Just one simple web service or API and you want the absolute minimum: consider App Runner (fully managed, opinionated, sits above all of this) - but the moment you need real control, you are back to ECS or EKS.
Do not pick EKS because it feels more "serious." Standard Kubernetes is a real advantage only if you will actually use the ecosystem or the portability. If you will not, ECS gives you the same reliability with a fraction of the operational load.
How networking and IAM plug in
Whatever you choose, two AWS systems wire it together: VPC networking and IAM. Getting these right is most of what "productionizing" a container platform means.
Networking. Both ECS and EKS run inside your VPC. The modern default is that each task or pod gets its own elastic network interface and a real VPC IP address (the awsvpc network mode on ECS; the AWS VPC CNI on EKS). That means containers are first-class citizens of your VPC: security groups apply to them directly, they sit in subnets you choose, and you keep them in private subnets with a load balancer in public subnets as the only front door. Inbound traffic almost always arrives through an Application Load Balancer - ECS Services register tasks into a target group automatically; on EKS the AWS Load Balancer Controller provisions an ALB from a Kubernetes Ingress. Outbound access to the internet (to pull images, call APIs) goes through a NAT gateway or, better for AWS APIs, VPC endpoints. The one recurring gotcha: a task that will not start or a health check that never passes is very often a security group that does not allow the load balancer to reach the container port, or the container in a subnet with no route to pull its image.
IAM, and the crucial task-role distinction. Containers get AWS permissions through IAM roles, and on ECS there are two different roles that beginners constantly conflate:
- Execution role - permissions ECS itself needs to launch the task: pull the image from ECR, write logs to CloudWatch, fetch secrets. This role is used by the platform, not your code.
- Task role - permissions your application code has at runtime: read this S3 bucket, write to this DynamoDB table, publish to this SQS queue. This is the role your container's AWS SDK calls assume.
The reason this matters is the whole point of IAM on containers: your code never needs baked-in AWS keys. The task role delivers temporary, automatically-rotated credentials to the container, and you scope that role to exactly the resources the service needs - least privilege, per service. EKS has the direct equivalent, IRSA (IAM Roles for Service Accounts) and the newer EKS Pod Identity, which map a Kubernetes service account to an IAM role so each pod assumes only its own permissions. Either way, the principle is identical to everything else in AWS: no long-lived keys in containers, a tightly-scoped role per workload. The AWS IAM guide covers how roles, policies, and temporary credentials work in depth - it is the foundation this sits on.
The shape of it
Running containers on AWS is three independent choices wearing a confusing pile of names. ECR holds your images and is barely a decision. ECS vs EKS is the real fork: ECS is the simpler, AWS-native orchestrator with less to run, while EKS is standard, portable Kubernetes with a much larger ecosystem and a correspondingly larger operational bill. EC2 vs Fargate is a separate dial about compute - Fargate removes servers entirely at a premium and applies to either orchestrator, while EC2 gives you control and better economics at scale. Default to ECS on Fargate unless you have a concrete reason to want Kubernetes or to own the instances. Then wire it into your VPC with tasks in private subnets behind a load balancer, and grant permissions through tightly-scoped task roles so no credentials ever live inside a container. Separate the three questions, answer each on its merits, and the maze becomes a short, defensible decision.