Container Services on AWS
AWS offers two managed container orchestration services:
| Service | What It Is | Best For |
|---|
| ECS (Elastic Container Service) | AWS-native container orchestrator | Teams wanting simplicity, AWS-native integration |
| EKS (Elastic Kubernetes Service) | Managed Kubernetes control plane | Teams 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
| Term | Meaning |
|---|
| Cluster | Logical grouping of tasks and services |
| Task Definition | Blueprint for a container — image, CPU, memory, ports, environment variables |
| Task | A running instance of a task definition (one or more containers) |
| Service | Keeps a desired number of tasks running, integrates with load balancers |
| Container Agent | Runs on EC2 nodes, communicates with ECS control plane |
Launch Types
| Launch Type | You Manage | AWS Manages |
|---|
| EC2 | EC2 instances, patching, scaling | Container scheduling |
| Fargate | Nothing — just define CPU/memory | EC2 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
| Mode | Description | Use With |
|---|
| awsvpc | Each task gets its own ENI and private IP | Fargate (required), EC2 (recommended) |
| bridge | Docker NAT networking on the host | EC2 only — legacy pattern |
| host | Container shares the EC2 host network | EC2 only — high performance, no port remapping |
awsvpc is the modern standard — tasks get their own security groups and IPs.
IAM Roles for Tasks
| Role | Purpose |
|---|
| Task Execution Role | ECS agent uses this to pull images from ECR, write logs to CloudWatch, fetch secrets |
| Task Role | Your 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
| Type | Description |
|---|
| Managed Node Groups | AWS manages node lifecycle — patches, drains, replaces during updates |
| Self-Managed Nodes | You manage EC2 instances manually |
| Fargate Profiles | Pods 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-on | Purpose |
|---|
| CoreDNS | In-cluster DNS resolution |
| kube-proxy | Network rules on nodes |
| Amazon VPC CNI | Pod networking — each pod gets a VPC IP |
| AWS Load Balancer Controller | Creates ALBs/NLBs from Kubernetes Ingress/Service |
| EBS CSI Driver | Mount EBS volumes as PersistentVolumes |
| EFS CSI Driver | Mount 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
| Factor | ECS | EKS |
|---|
| Complexity | Low — AWS-native, simpler | Higher — full Kubernetes |
| Learning curve | Easy | Steep (requires K8s knowledge) |
| Portability | AWS-only | Portable — runs anywhere K8s runs |
| Ecosystem | AWS tooling | Massive K8s ecosystem (Helm, Argo, etc.) |
| Features | Simpler, covers most use cases | Full K8s — advanced scheduling, custom resources |
| Fargate support | Yes | Yes (via Fargate profiles) |
| Cost | No control plane cost | $0.10/hr per cluster for managed control plane |
| Best for | Greenfield AWS workloads, microservices | Existing 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
|