AWS Elastic Container Service (ECS) and EKS

How Amazon ECS and Amazon EKS run containers on AWS, what Fargate, Managed Instances and Auto Mode change, and how to choose between them.

Cloud

Once you have a container image in a registry, AWS gives you two orchestrators to run it: Amazon Elastic Container Service (ECS) and Amazon Elastic Kubernetes Service (EKS). Both schedule containers across a fleet of machines, restart them when they fail, roll out new versions and plug into load balancers and IAM. They differ in where the rules come from. ECS is AWS’s own design and speaks only AWS APIs. EKS runs upstream Kubernetes, so it speaks the same API as every other Kubernetes cluster in the world.

This article picks up where Introduction to Containers and Docker left off. It explains how each service is put together, the compute options underneath them, how identity and networking work, what you pay for, and how to decide which one fits a given team.

Two orchestrators, one platform

An orchestrator answers a handful of questions continuously: which machine should this container run on, is it healthy, how many copies should exist, and how does traffic reach them. ECS and EKS both answer those questions, and both rely on the same AWS building blocks underneath: VPC networking, EC2 instances or Fargate for compute, Elastic Load Balancing for traffic, IAM for permissions, CloudWatch for logs and metrics, and Amazon ECR for images.

flowchart TB
    ECR["Amazon ECR images"] --> ECS["Amazon ECS control plane"]
    ECR --> EKS["Amazon EKS control plane"]
    ECS --> FG["AWS Fargate"]
    ECS --> MI["ECS Managed Instances"]
    ECS --> EC2A["Your EC2 Auto Scaling groups"]
    EKS --> AUTO["EKS Auto Mode nodes"]
    EKS --> MNG["Managed node groups"]
    EKS --> FGK["Fargate for EKS"]
    FG --> ALB["Elastic Load Balancing"]
    MI --> ALB
    EC2A --> ALB
    AUTO --> ALB
    MNG --> ALB
    FGK --> ALB

The control plane is the part AWS always runs for you. With ECS you never see it; it is a regional service you call through the API. With EKS you get a dedicated Kubernetes control plane per cluster, which AWS runs across multiple Availability Zones and patches for you, and which you reach with kubectl. What you choose, in both cases, is the data plane: the compute your containers actually run on.

How Amazon ECS works

ECS has a small vocabulary, and learning it is most of the work.

  • Cluster: a logical grouping of capacity and services. It costs nothing on its own.
  • Task definition: a versioned JSON document that describes one or more containers: image, CPU and memory, ports, environment variables, secrets, logging and the IAM roles to use. Each change creates a new revision (web:1, web:2).
  • Task: a running instance of a task definition. The containers in one task are scheduled together on the same host and share a network namespace when they use the awsvpc network mode.
  • Service: keeps a desired number of tasks running, replaces failed ones, registers them with a load balancer target group and performs deployments.
  • Capacity provider: where the tasks run: Fargate, Fargate Spot, ECS Managed Instances or an Auto Scaling group of EC2 instances you manage.

Here is a task definition for the Flask image from the previous article, running on Fargate:

{
  "family": "web",
  "requiresCompatibilities": ["FARGATE"],
  "networkMode": "awsvpc",
  "cpu": "512",
  "memory": "1024",
  "runtimePlatform": {
    "cpuArchitecture": "ARM64",
    "operatingSystemFamily": "LINUX"
  },
  "executionRoleArn": "arn:aws:iam::123456789012:role/ecsTaskExecutionRole",
  "taskRoleArn": "arn:aws:iam::123456789012:role/web-task-role",
  "containerDefinitions": [
    {
      "name": "web",
      "image": "123456789012.dkr.ecr.us-east-1.amazonaws.com/hello-web:1.0",
      "essential": true,
      "portMappings": [{ "containerPort": 8080, "protocol": "tcp" }],
      "environment": [{ "name": "APP_ENV", "value": "production" }],
      "secrets": [
        {
          "name": "DATABASE_URL",
          "valueFrom": "arn:aws:secretsmanager:us-east-1:123456789012:secret:web/db-url"
        }
      ],
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "/ecs/web",
          "awslogs-region": "us-east-1",
          "awslogs-stream-prefix": "web"
        }
      }
    }
  ]
}

Two roles appear in that file, and mixing them up is one of the most common ECS mistakes:

  • The task execution role is used by the ECS agent, not your code. It lets ECS pull the image from ECR, write logs to CloudWatch and fetch the secrets named in the task definition.
  • The task role is what your application code assumes. If the Flask app reads from S3 or writes to DynamoDB, those permissions go here, and the AWS SDK inside the container picks up temporary credentials automatically.

Keep the task role narrow, following the least-privilege habits from AWS Identity and Access Management (IAM).

With cpuArchitecture set to ARM64 in runtimePlatform, the task runs on Graviton. That only works if the image was built for linux/arm64, which is why the multi-platform docker buildx advice from the previous article matters here.

Creating a cluster and a service

From the CLI, registering the task definition and running it behind an existing Application Load Balancer target group looks like this:

aws ecs create-cluster --cluster-name demo \
  --capacity-providers FARGATE FARGATE_SPOT

aws ecs register-task-definition --cli-input-json file://web-task.json

aws ecs create-service \
  --cluster demo \
  --service-name web \
  --task-definition web \
  --desired-count 4 \
  --capacity-provider-strategy \
      capacityProvider=FARGATE,base=2,weight=1 \
      capacityProvider=FARGATE_SPOT,weight=1 \
  --network-configuration "awsvpcConfiguration={subnets=[subnet-0a1b,subnet-0c2d],securityGroups=[sg-0e3f],assignPublicIp=DISABLED}" \
  --load-balancers "targetGroupArn=arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/web/abc123,containerName=web,containerPort=8080"

The capacity provider strategy keeps a base of two tasks on regular Fargate and splits the rest evenly between Fargate and Fargate Spot. Spot capacity is cheaper but can be reclaimed with a two-minute warning, so it suits stateless web tasks behind a load balancer and doesn’t suit a single long-running job that can’t be restarted. Pricing Models: On-Demand, Reserved, Savings Plans, and Spot covers the same trade-off for VMs.

In practice you would describe all of this in CloudFormation, the CDK or Terraform rather than typing it, but the CLI shows how few moving parts a service really has.

Express Mode

If even that is too much setup, ECS Express Mode, announced in November 2025, collapses it into one step. You provide a container image and a few settings, and ECS creates the cluster, task definition, Application Load Balancer, auto scaling policy and a URL for the service. There is no extra charge for Express Mode itself; you pay for the resources it creates. It is the closest AWS comes to Cloud Run or Azure Container Apps, and because the result is ordinary ECS resources, you can take over and customize them later.

Deployments and service-to-service traffic

A service deploys a new task definition revision with a rolling update by default: it starts new tasks, waits for them to pass load balancer health checks, then drains the old ones. Two settings control the pace, minimumHealthyPercent and maximumPercent. The deployment circuit breaker watches for new tasks that keep failing and rolls back to the last working revision automatically, which is worth turning on for every service. ECS also supports blue/green deployments, where the new version runs alongside the old and traffic shifts only after it passes your checks.

For calls between services, ECS Service Connect gives each service a short name inside a namespace and adds a managed proxy that handles discovery, retries and connection metrics. Your code calls http://orders:8080 instead of an internal load balancer address.

Your container starts, but its code gets AccessDenied when it reads from an S3 bucket. Which role needs the S3 permission?

  • The task execution role
  • The task role
  • The IAM role of the ECS cluster

Correct! The task role supplies credentials to your application code. The execution role is only for ECS itself: pulling images, writing logs and fetching secrets.

Where ECS tasks run

The capacity provider decides how much of the server layer you see. There are three main choices.

AWS Fargate runs each task in its own isolated environment with no instances to manage. You set CPU and memory at the task level, and you pay for those resources per second while the task runs. Each task gets 20 GiB of ephemeral storage by default, which you can raise to 200 GiB. Fargate is the default answer for most ECS services because there is nothing to patch.

ECS Managed Instances sits between Fargate and doing it yourself. ECS launches, patches and replaces real EC2 instances in your account, and you can choose instance types by attributes such as vCPU count, memory or CPU manufacturer, including GPU instances. Several tasks can share one instance, which makes better use of large machines. You pay the EC2 price plus a management charge.

EC2 Auto Scaling group capacity providers give you full control: your own AMI, your own instance types, your own daemons on every host. You also own the patching, AMI updates and scaling of that fleet, the work described in Auto Scaling VMs on AWS, GCP, and Azure. Choose this when you need something the other two don’t offer, such as a specific kernel setting or agent.

ECS also has an External option, ECS Anywhere, for registering servers outside AWS, such as on-premises machines, so they run tasks under the same control plane.

FargateECS Managed InstancesEC2 Auto Scaling group
Who patches the hostAWSAWSYou
Instance choiceNone, you size the taskAttribute-based selectionAny instance type and AMI
Tasks per hostOne task per isolated environmentManyMany
GPU workloadsNoYesYes
BillingTask vCPU and memory per secondEC2 price plus management chargeEC2 price
Best forMost web services and jobsLarge or specialized instances without host choresCustom hosts and full control

AWS now recommends using capacity providers, rather than the older launchType parameter, when you start tasks and services; the launch type only declares which kinds of capacity a task definition is compatible with.

How Amazon EKS works

EKS gives you a standard Kubernetes cluster. AWS runs the control plane (the API server, etcd, the scheduler and the controller managers) across multiple Availability Zones, and you interact with it through the Kubernetes API. Everything you know about Deployments, Services, ConfigMaps, Helm charts and operators applies unchanged.

flowchart LR
    DEV["kubectl or CI"] -->|"Kubernetes API"| CP["EKS control plane"]
    CP --> N1["Node in AZ a"]
    CP --> N2["Node in AZ b"]
    N1 --> P1["Pods"]
    N2 --> P2["Pods"]
    P1 --> VPC["VPC IP addresses"]
    P2 --> VPC
    LBC["Load balancer controller"] --> ALB["ALB or NLB"]
    ALB --> P1
    ALB --> P2

AWS-specific behavior arrives through add-ons and controllers. The Amazon VPC CNI gives every pod a real IP address from your VPC subnets, so pods are directly reachable inside the VPC and security groups can apply to them. That is convenient, but it also means a busy cluster can run a subnet out of addresses, so size the subnets generously. The AWS Load Balancer Controller turns Kubernetes Ingress and Service objects into Application and Network Load Balancers. The EBS CSI driver provides persistent volumes.

Data plane choices

EKS has more ways to supply nodes than ECS, which reflects how flexible Kubernetes is.

  • EKS Auto Mode is the most hands-off option. AWS provisions EC2 instances in response to pending pods using a Karpenter-based system, and runs the load balancer controller, the EBS CSI driver and other core components for you, off the cluster. Nodes run Bottlerocket, a minimal container OS, and are replaced after at most 21 days so they stay current. You pay the EC2 price plus an Auto Mode management charge per instance.
  • Managed node groups are Auto Scaling groups that EKS creates and updates for you, with rolling replacement when you upgrade the AMI. You still choose instance types and decide when to update.
  • Self-managed nodes are EC2 instances you join to the cluster yourself, often scaled by Karpenter running in the cluster. This is the most control and the most work.
  • Fargate for EKS runs each pod in its own isolated environment. It suits small, spiky workloads, but DaemonSets don’t run there, and each pod is billed and sized individually.

EKS Hybrid Nodes can also attach on-premises machines to a cluster whose control plane runs in AWS.

Starting a cluster

eksctl, the official CLI for EKS, can create an Auto Mode cluster and a VPC with one command:

eksctl create cluster --name demo --region us-east-1 --enable-auto-mode

kubectl get nodes   # empty until a pod needs capacity

Then you deploy with ordinary Kubernetes manifests. This one runs the same image with three replicas and asks for a load balancer:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      serviceAccountName: web
      containers:
        - name: web
          image: 123456789012.dkr.ecr.us-east-1.amazonaws.com/hello-web:1.0
          ports:
            - containerPort: 8080
          resources:
            requests:
              cpu: 250m
              memory: 256Mi
            limits:
              memory: 512Mi
          readinessProbe:
            httpGet:
              path: /healthz
              port: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: web
spec:
  type: LoadBalancer
  selector:
    app: web
  ports:
    - port: 80
      targetPort: 8080

Resource requests matter more on EKS than they first seem to. The scheduler places pods by their requests, and Auto Mode or Karpenter chooses instance sizes from the requests of pending pods. Requests that are too low pack nodes until they run out of memory; requests that are too high waste money.

Identity: two kinds of access

Kubernetes clusters have two separate identity questions, and EKS answers each with an AWS feature.

Who can use the cluster. EKS access entries map IAM users and roles to Kubernetes permissions through the EKS API. They replace the older aws-auth ConfigMap, which was easy to break with a single bad edit.

What pods can do in AWS. EKS Pod Identity links a Kubernetes service account to an IAM role, so every pod using that service account receives temporary credentials for that role:

aws eks create-pod-identity-association \
  --cluster-name demo \
  --namespace default \
  --service-account web \
  --role-arn arn:aws:iam::123456789012:role/web-pod-role

AWS recommends Pod Identity for new setups. The older approach, IAM Roles for Service Accounts (IRSA), uses an OIDC provider per cluster and annotations on the service account; it still works, and both can run in one cluster while you migrate. Pod Identity plays the same part on EKS as the task role does on ECS.

An EKS cluster suddenly can't schedule new pods even though nodes have spare CPU and memory. Pods report IP address allocation errors. What is the likely cause?

  • The VPC subnets used by the cluster are running out of free IP addresses
  • The EKS control plane has reached its pod limit
  • Pod Identity associations are missing

Correct! The Amazon VPC CNI gives each pod an IP address from your subnets, so small subnets limit how many pods can run.

Upgrades and the version clock

ECS has no version to manage: AWS evolves the control plane and you adopt new features when you want them. Your hosts still need patching unless you use Fargate or Managed Instances.

EKS follows the Kubernetes release cycle. Each Kubernetes minor version gets 14 months of standard support on EKS, followed by 12 months of extended support at a higher price. Extended support is on by default. If you set the cluster’s upgrade policy to standard, EKS upgrades the control plane automatically when standard support ends instead of moving it into extended support. Either way, the automatic upgrade covers only the control plane; you still update add-ons and nodes yourself unless Auto Mode handles them.

That makes upgrades a recurring task for EKS teams: read the Kubernetes changelog for removed APIs, upgrade the control plane one minor version at a time, then the add-ons and nodes. Plan for it roughly every few months rather than treating it as a surprise.

What you pay for

Neither service charges for the containers themselves. You pay for the compute, load balancers, storage, data transfer and logs either way. The differences sit on top of that:

ChargeAmazon ECSAmazon EKS
Control planeNo charge$0.10 per cluster per hour on standard support, $0.60 on extended support
FargateTask vCPU and memory per secondPod vCPU and memory per second
Managed EC2 optionManaged Instances: EC2 price plus a management chargeAuto Mode: EC2 price plus a management charge
Self-managed EC2EC2 price onlyEC2 price only

The EKS control plane fee works out to about $73 a month per cluster on standard support, which is small for a production platform and noticeable when every developer or every branch gets its own cluster. Check the Amazon EKS pricing and AWS Fargate pricing pages for current rates in your Region.

The larger cost difference is usually people. A Kubernetes platform needs someone who understands upgrades, add-ons, networking and the ecosystem tools you install. ECS has far fewer knobs, so a small team spends less time on the platform and more on the application.

Choosing between ECS and EKS

flowchart TD
    Q1{"Already run Kubernetes, or need to run on several clouds?"}
    Q1 -->|"Yes"| EKS["Amazon EKS"]
    Q1 -->|"No"| Q2{"Need Helm charts, operators or other Kubernetes tools?"}
    Q2 -->|"Yes"| EKS
    Q2 -->|"No"| Q3{"Want the fewest platform decisions?"}
    Q3 -->|"Yes"| EXP["ECS with Fargate or Express Mode"]
    Q3 -->|"No"| ECS["ECS with Managed Instances or EC2"]

Pick ECS when AWS is your only cloud, the team is small or new to containers, and you want services running this week. It integrates tightly with IAM, load balancers and CloudWatch, has no version upgrades to schedule, and costs nothing beyond the compute. Most stateless web services, APIs and background workers fit comfortably.

Pick EKS when you already use Kubernetes elsewhere, want portable manifests and Helm charts, depend on Kubernetes operators (databases, message brokers, ML training frameworks), or need fine-grained scheduling such as node affinity and custom controllers. The extra power comes with an upgrade cycle and a larger surface to learn, though Auto Mode removes much of the node work.

Both are production-grade, and many companies run both: ECS for straightforward services and EKS for platforms built around the Kubernetes ecosystem. If you are unsure, start with ECS on Fargate. Moving a well-built container to Kubernetes later is mostly a matter of rewriting the deployment files, because the image stays the same.

A team has Helm charts and Kubernetes manifests from an on-premises cluster and wants to move them to AWS with minimal rewriting. Which service fits best?

  • Amazon ECS with Fargate
  • ECS Express Mode
  • Amazon EKS

Correct! EKS runs upstream Kubernetes, so existing manifests and Helm charts apply with little change. ECS uses its own task definitions.

Further reading

Key takeaways

  • ECS and EKS both orchestrate containers on AWS; ECS uses AWS’s own simpler model, and EKS runs upstream Kubernetes.
  • In ECS, a task definition describes containers, a service keeps tasks running, and a capacity provider decides whether they run on Fargate, Managed Instances or your own EC2 fleet.
  • Use the task role (ECS) or Pod Identity (EKS) for application permissions; the ECS execution role is only for pulling images, writing logs and fetching secrets.
  • EKS Auto Mode, managed node groups, self-managed nodes and Fargate trade control for convenience; Auto Mode handles node provisioning and core add-ons for you.
  • ECS has no control plane fee and no version upgrades. EKS charges per cluster hour and needs a Kubernetes upgrade routine, with 14 months of standard support per version.
  • Start with ECS on Fargate unless you need Kubernetes compatibility, its ecosystem or multi-cloud portability; the container image carries over either way.

Part 2 of 2 · Container Services

  1. 01Introduction to Containers and Docker
  2. 02AWS Elastic Container Service (ECS) and EKS
Ready to check your understanding?Take the Fluffy Computing quiz
esc

Type to search all articles · ↑↓ to move · ↵ to open