Post

AWS ECS and EKS — Containers, Fargate, and Managed Kubernetes

A full walkthrough of AWS container services — ECS with EC2 and Fargate launch types, task definitions, services, and EKS managed Kubernetes with node groups and IRSA

AWS ECS and EKS — Containers, Fargate, and Managed Kubernetes

Container Services on AWS

AWS offers two managed container orchestration services:

ServiceWhat It IsBest For
ECS (Elastic Container Service)AWS-native container orchestratorTeams wanting simplicity, AWS-native integration
EKS (Elastic Kubernetes Service)Managed Kubernetes control planeTeams with existing K8s expertise, multi-cloud

Both support Fargate — a serverless compute engine where you don’t manage EC2 instances at all.


ECS — Elastic Container Service

Core Concepts

TermMeaning
ClusterLogical grouping of tasks and services
Task DefinitionBlueprint for a container — image, CPU, memory, ports, environment variables
TaskA running instance of a task definition (one or more containers)
ServiceKeeps a desired number of tasks running, integrates with load balancers
Container AgentRuns on EC2 nodes, communicates with ECS control plane

Launch Types

Launch TypeYou ManageAWS Manages
EC2EC2 instances, patching, scalingContainer scheduling
FargateNothing — just define CPU/memoryEC2 instances, OS, patching

Fargate is the recommended default — no infrastructure to manage. Use EC2 launch type when you need GPU instances, specific placement, or Windows containers with network mode host.


Task Definitions

A task definition is a JSON document describing how containers should run. It defines the container image, CPU and memory, networking mode, volumes, environment variables, logging, and IAM role.

📸 SCREENSHOT: ECS → Task Definitions → Create New Task Definition. Show the launch type selection (Fargate), then the container definition section with image URI, port mappings, and environment variables.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
{
  "family": "web-app",
  "networkMode": "awsvpc",
  "requiresCompatibilities": ["FARGATE"],
  "cpu": "512",
  "memory": "1024",
  "executionRoleArn": "arn:aws:iam::123456789012:role/ecs-task-execution-role",
  "taskRoleArn": "arn:aws:iam::123456789012:role/web-app-task-role",
  "containerDefinitions": [
    {
      "name": "web",
      "image": "123456789012.dkr.ecr.eu-west-1.amazonaws.com/web-app:latest",
      "portMappings": [{"containerPort": 8080, "protocol": "tcp"}],
      "environment": [
        {"name": "ENV", "value": "production"},
        {"name": "DB_HOST", "value": "db.internal"}
      ],
      "secrets": [
        {"name": "DB_PASSWORD", "valueFrom": "arn:aws:secretsmanager:...:secret:db-password"}
      ],
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "/ecs/web-app",
          "awslogs-region": "eu-west-1",
          "awslogs-stream-prefix": "web"
        }
      },
      "healthCheck": {
        "command": ["CMD-SHELL", "curl -f http://localhost:8080/health || exit 1"],
        "interval": 30,
        "timeout": 5,
        "retries": 3
      }
    }
  ]
}
1
2
3
4
5
# Register a task definition
aws ecs register-task-definition --cli-input-json file://task-def.json

# List task definitions
aws ecs list-task-definitions --family-prefix web-app --output table

Networking Modes

ModeDescriptionUse With
awsvpcEach task gets its own ENI and private IPFargate (required), EC2 (recommended)
bridgeDocker NAT networking on the hostEC2 only — legacy pattern
hostContainer shares the EC2 host networkEC2 only — high performance, no port remapping

awsvpc is the modern standard — tasks get their own security groups and IPs.

IAM Roles for Tasks

RolePurpose
Task Execution RoleECS agent uses this to pull images from ECR, write logs to CloudWatch, fetch secrets
Task RoleYour application container uses this to call AWS APIs (S3, DynamoDB, etc.)
1
2
3
4
5
6
7
8
9
10
11
12
# Minimum task execution role policy
{
  "Effect": "Allow",
  "Action": [
    "ecr:GetAuthorizationToken",
    "ecr:BatchCheckLayerAvailability",
    "ecr:GetDownloadUrlForLayer",
    "ecr:BatchGetImage",
    "logs:CreateLogStream",
    "logs:PutLogEvents"
  ]
}

ECS Services

A service maintains a desired number of running tasks and replaces failed tasks automatically. Services integrate with Application Load Balancers to distribute traffic across tasks.

📸 SCREENSHOT: ECS → Clusters → select cluster → Services → Create. Show the task definition dropdown, desired tasks field, and the Load Balancer section where you select the ALB and target group.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# Create a Fargate service
aws ecs create-service \
  --cluster prod-cluster \
  --service-name web-service \
  --task-definition web-app:5 \
  --desired-count 3 \
  --launch-type FARGATE \
  --network-configuration 'awsvpcConfiguration={
    subnets=[subnet-private-1a,subnet-private-1b],
    securityGroups=[sg-ecs-tasks],
    assignPublicIp=DISABLED
  }' \
  --load-balancers 'targetGroupArn=arn:aws:elasticloadbalancing:...:targetgroup/web-tg/abc,containerName=web,containerPort=8080'

# Update service (rolling deploy new task definition)
aws ecs update-service \
  --cluster prod-cluster \
  --service web-service \
  --task-definition web-app:6

# Scale service
aws ecs update-service \
  --cluster prod-cluster \
  --service web-service \
  --desired-count 5

Service Auto Scaling

ECS services support Application Auto Scaling — scale task count based on CloudWatch metrics.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Register scalable target
aws application-autoscaling register-scalable-target \
  --service-namespace ecs \
  --resource-id service/prod-cluster/web-service \
  --scalable-dimension ecs:service:DesiredCount \
  --min-capacity 2 \
  --max-capacity 20

# Target tracking — keep CPU at 60%
aws application-autoscaling put-scaling-policy \
  --service-namespace ecs \
  --resource-id service/prod-cluster/web-service \
  --scalable-dimension ecs:service:DesiredCount \
  --policy-name cpu-tracking \
  --policy-type TargetTrackingScaling \
  --target-tracking-scaling-policy-configuration '{
    "PredefinedMetricSpecification": {"PredefinedMetricType": "ECSServiceAverageCPUUtilization"},
    "TargetValue": 60.0
  }'

ECR — Elastic Container Registry

ECR is AWS’s managed Docker container registry. Private by default, integrates with IAM for access control.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Authenticate Docker to ECR
aws ecr get-login-password --region eu-west-1 \
  | docker login --username AWS --password-stdin \
    123456789012.dkr.ecr.eu-west-1.amazonaws.com

# Create a repository
aws ecr create-repository --repository-name web-app

# Build, tag, and push
docker build -t web-app .
docker tag web-app:latest 123456789012.dkr.ecr.eu-west-1.amazonaws.com/web-app:latest
docker push 123456789012.dkr.ecr.eu-west-1.amazonaws.com/web-app:latest

# List images
aws ecr list-images --repository-name web-app --output table

Enable lifecycle policies to automatically delete old images and control storage costs:

1
2
3
4
5
6
7
8
9
10
11
12
{
  "rules": [{
    "rulePriority": 1,
    "description": "Keep last 10 images",
    "selection": {
      "tagStatus": "any",
      "countType": "imageCountMoreThan",
      "countNumber": 10
    },
    "action": {"type": "expire"}
  }]
}

EKS — Elastic Kubernetes Service

EKS is AWS’s managed Kubernetes service. AWS runs and manages the Kubernetes control plane (API server, etcd, scheduler). You manage the worker nodes (or use Fargate for serverless pods).

If you already know Kubernetes, EKS gives you the full K8s API with AWS integrations handled for you.

📸 SCREENSHOT: EKS → Clusters → Create Cluster. Show the cluster name, Kubernetes version selector, cluster IAM role, and the VPC/subnets configuration section.

EKS Cluster Creation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Create a cluster (using eksctl — recommended)
eksctl create cluster \
  --name prod-cluster \
  --region eu-west-1 \
  --version 1.29 \
  --nodegroup-name general \
  --node-type m5.xlarge \
  --nodes 3 \
  --nodes-min 2 \
  --nodes-max 10 \
  --managed

# Configure kubectl
aws eks update-kubeconfig --name prod-cluster --region eu-west-1

# Verify
kubectl get nodes
kubectl get pods --all-namespaces

Node Groups

TypeDescription
Managed Node GroupsAWS manages node lifecycle — patches, drains, replaces during updates
Self-Managed NodesYou manage EC2 instances manually
Fargate ProfilesPods run on Fargate — no node management at all

Managed node groups are the standard choice. Use Fargate profiles for specific workloads (batch jobs, dev namespaces) where you want zero node management.

1
2
3
4
5
6
7
8
9
# Add a managed node group
eksctl create nodegroup \
  --cluster prod-cluster \
  --name compute-ng \
  --node-type c5.2xlarge \
  --nodes 3 \
  --nodes-min 1 \
  --nodes-max 10 \
  --managed

EKS Add-ons

EKS add-ons are AWS-managed Kubernetes components that AWS keeps patched:

Add-onPurpose
CoreDNSIn-cluster DNS resolution
kube-proxyNetwork rules on nodes
Amazon VPC CNIPod networking — each pod gets a VPC IP
AWS Load Balancer ControllerCreates ALBs/NLBs from Kubernetes Ingress/Service
EBS CSI DriverMount EBS volumes as PersistentVolumes
EFS CSI DriverMount EFS as shared PersistentVolumes
1
2
3
4
5
6
7
# Install AWS Load Balancer Controller via Helm
helm repo add eks https://aws.github.io/eks-charts
helm install aws-load-balancer-controller eks/aws-load-balancer-controller \
  --set clusterName=prod-cluster \
  --set serviceAccount.create=false \
  --set serviceAccount.name=aws-load-balancer-controller \
  -n kube-system

IRSA — IAM Roles for Service Accounts

IRSA lets Kubernetes pods assume IAM roles without storing credentials. A Kubernetes service account is annotated with an IAM role ARN. Pods using that service account get AWS credentials via the pod’s projected service account token.

This is the standard way to give pods access to S3, DynamoDB, Secrets Manager, etc.

1
2
3
4
5
6
7
8
9
10
11
12
# Create IAM OIDC provider for the cluster
eksctl utils associate-iam-oidc-provider \
  --cluster prod-cluster \
  --approve

# Create an IAM role for a service account
eksctl create iamserviceaccount \
  --cluster prod-cluster \
  --namespace default \
  --name s3-reader \
  --attach-policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess \
  --approve
1
2
3
4
5
6
7
8
# Pod using the service account
apiVersion: v1
kind: Pod
spec:
  serviceAccountName: s3-reader
  containers:
  - name: app
    image: my-app:latest

📸 SCREENSHOT: EKS → Clusters → select cluster → Configuration → Authentication. Show the OIDC provider URL and the IAM OIDC identity provider that enables IRSA.


ECS vs EKS — When to Use Which

FactorECSEKS
ComplexityLow — AWS-native, simplerHigher — full Kubernetes
Learning curveEasySteep (requires K8s knowledge)
PortabilityAWS-onlyPortable — runs anywhere K8s runs
EcosystemAWS toolingMassive K8s ecosystem (Helm, Argo, etc.)
FeaturesSimpler, covers most use casesFull K8s — advanced scheduling, custom resources
Fargate supportYesYes (via Fargate profiles)
CostNo control plane cost$0.10/hr per cluster for managed control plane
Best forGreenfield AWS workloads, microservicesExisting K8s workloads, large teams, multi-cloud

Useful Commands

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# ECS
aws ecs list-clusters
aws ecs list-services --cluster prod-cluster
aws ecs describe-services --cluster prod-cluster --services web-service
aws ecs list-tasks --cluster prod-cluster --service-name web-service
aws ecs describe-tasks --cluster prod-cluster --tasks <task-arn>

# ECS exec (interactive shell into a running container — like kubectl exec)
aws ecs execute-command \
  --cluster prod-cluster \
  --task <task-arn> \
  --container web \
  --interactive \
  --command "/bin/bash"

# EKS / kubectl
kubectl get pods -n production
kubectl logs -f deployment/web-app -n production
kubectl exec -it pod/web-app-xxx -- /bin/bash
kubectl describe pod web-app-xxx
kubectl rollout status deployment/web-app
kubectl rollout undo deployment/web-app
This post is licensed under CC BY 4.0 by the author.