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. FARGATE, FARGATE_SPOT, or an EC2 Auto Scaling Group registered as a provider.

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

FARGATE

Serverless containers. AWS provides the capacity; the operator pays per task vCPU / memory / second. No host management.

FARGATE_SPOT

Same model, interruptible, ~70% discount. Right for batch and stateless services that tolerate a 2-minute eviction notice.

EC2

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 minimumHealthyPercent and maximumPercent. 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. awslogs ships 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#