ECS#
Amazon Elastic Container Service is AWS’s native container
orchestrator. The control plane is fully managed (no etcd, no
scheduler to operate); the operator brings a launch type
(EC2 instances or FARGATE) and submits Task Definitions
and Services. The path of least resistance for AWS-only estates
where the operator does not want a Kubernetes cluster.
Architecture#
flowchart LR
OP[operator] -->|API / CLI / Console| ECS[(ECS control plane)]
ECS --> SVC[Service]
SVC --> T1[Task]
SVC --> T2[Task]
SVC --> T3[Task]
T1 -.runs on.-> CAP{Capacity}
CAP --> EC2[EC2 instance + ecs-agent]
CAP --> FAR[Fargate, serverless]
SVC -. registers .- ELB[ALB / NLB target group]
Concept |
Detail |
|---|---|
Cluster |
Logical grouping of compute capacity. One cluster typically holds many services. |
Capacity provider |
Where tasks run. |
Task Definition |
Versioned JSON describing a pod-like unit: containers, images, CPU/memory, ports, environment, IAM role. |
Task |
One running instance of a Task Definition. |
Service |
Maintains N tasks of a Task Definition, performs rolling deploys, registers with a load balancer. |
ecs-agent |
Runs on every EC2 capacity instance. Talks to the ECS control plane the way kubelet talks to kube-apiserver. |
Task Definition#
The operator-facing primitive. Compose-style but more constrained.
{
"family": "web",
"networkMode": "awsvpc",
"requiresCompatibilities": ["FARGATE"],
"cpu": "512",
"memory": "1024",
"executionRoleArn": "arn:aws:iam::123:role/ecsTaskExecutionRole",
"taskRoleArn": "arn:aws:iam::123:role/web-task",
"containerDefinitions": [
{
"name": "web",
"image": "123.dkr.ecr.us-east-1.amazonaws.com/web:1.0",
"essential": true,
"portMappings": [{ "containerPort": 8080, "protocol": "tcp" }],
"environment": [{ "name": "LOG_LEVEL", "value": "info" }],
"secrets": [{ "name": "DB_URL", "valueFrom": "arn:aws:ssm:...:parameter/web/db_url" }],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/web",
"awslogs-region": "us-east-1",
"awslogs-stream-prefix": "ecs"
}
},
"healthCheck": {
"command": ["CMD-SHELL", "curl -f http://localhost:8080/healthz || exit 1"],
"interval": 10, "timeout": 2, "retries": 3
}
}
]
}
Register and run:
$ aws ecs register-task-definition --cli-input-json file://web.json
$ aws ecs create-service \
--cluster prod \
--service-name web \
--task-definition web \
--desired-count 3 \
--launch-type FARGATE \
--network-configuration 'awsvpcConfiguration={subnets=[subnet-aaa,subnet-bbb],securityGroups=[sg-...],assignPublicIp=DISABLED}' \
--load-balancers 'targetGroupArn=arn:aws:elasticloadbalancing:...,containerName=web,containerPort=8080'
Launch types#
Type |
Detail |
|---|---|
|
Serverless containers. AWS provides the capacity; the operator pays per task vCPU / memory / second. No host management. |
|
Same model, interruptible, ~70% discount. Right for batch and stateless services that tolerate a 2-minute eviction notice. |
|
Operator-managed EC2 instances in an Auto Scaling Group registered as a capacity provider. Lower per-CPU cost; the operator now manages the host OS. |
External (ECS Anywhere) |
Run the ECS agent on operator-owned servers (on-prem, edge). Control plane stays in AWS. |
Networking#
awsvpc mode is the standard. Every task gets its own ENI in
the VPC, with VPC security groups applied at the task level.
bridge and host modes are legacy EC2-only options.
For ingress, register the Service against an Application Load Balancer (HTTP / HTTPS) or Network Load Balancer (TCP / UDP / TLS) target group; ECS keeps the target group in sync with the running tasks.
Deployments#
Rolling update (default): respect
minimumHealthyPercentandmaximumPercent. Replace tasks in batches.Blue/green via CodeDeploy: ECS creates a second target group; CodeDeploy shifts traffic between them; the operator validates before completing the cutover.
External deployment controller: the operator drives task set transitions directly through the API (rare; mostly for custom GitOps integrations).
$ aws ecs update-service --cluster prod --service web \
--task-definition web:42 \
--deployment-configuration 'minimumHealthyPercent=100,maximumPercent=200'
Service discovery and observability#
Service Connect: ECS’s native service mesh. Each task gets an Envoy sidecar; services reach each other by name; mTLS and metrics are automatic.
Cloud Map: DNS-based service discovery without sidecars.
CloudWatch Logs: the default log driver.
awslogsships every container’s stdout / stderr to a log group.CloudWatch Container Insights: cluster, service, task metrics + tracing. Enable per cluster.
X-Ray: distributed tracing.
Secrets and IAM#
Two IAM roles per task definition:
Execution role (
executionRoleArn): what ECS itself uses to pull images, fetch secrets, write logs. Pull access to ECR and read access to the secret stores.Task role (
taskRoleArn): what the running container uses. Whatever AWS APIs the workload needs (S3, DynamoDB, SQS).
Secrets resolve from SSM Parameter Store or Secrets Manager; ECS fetches them at task start and injects them as env vars or files.
When to pick ECS#
The estate is AWS-only and the operator wants the path of least resistance.
The team is small and Kubernetes operational overhead is not on the budget.
Fargate’s per-pod billing is a better fit than running an EKS cluster with persistent node groups.
When not to#
Multi-cloud, multi-region active-active, or any environment that demands a portable orchestrator.
Heavy use of CRDs, operators, Helm charts. The Kubernetes ecosystem does not transfer.
References#
Kubernetes (and EKS in Deploy) for the comparison point.