TL;DR
- ECS (Elastic Container Service) is AWS's container orchestrator - it runs and keeps your containers alive.
- A task definition is the blueprint (image, CPU/memory, env, IAM role); a task is a running instance; a service keeps N tasks running.
- Fargate is serverless containers - no EC2 hosts to patch or scale.
- An Application Load Balancer + target group routes traffic to healthy tasks.
Where I am right now
This is current - I'm actively learning ECS this week. I have reproducible Docker images from earlier in the journey and a CI/CD pipeline that builds them. The missing piece was running them somewhere that isn't a server I SSH into. ECS is that somewhere, and it ties together almost everything I've learned: containers, statelessness, load balancing, and IAM.
The ECS vocabulary that confused me at first
ECS has four nested concepts, and getting them straight made everything else click:
- Cluster - a logical grouping of compute where your containers run.
- Task definition - the blueprint. Which image, how much CPU/memory, env vars, ports, logging, and which IAM roles. Think of it like a
docker runspec, versioned. - Task - a running instance of a task definition (one or more containers scheduled together).
- Service - the supervisor. It maintains a desired count of tasks, replaces unhealthy ones, and integrates with a load balancer for rolling deploys.
So: the service runs N tasks from a task definition inside a cluster. Once I had that sentence memorized, the console stopped being a maze.
A task definition, concretely
{
"family": "taskflow-api",
"networkMode": "awsvpc",
"requiresCompatibilities": ["FARGATE"],
"cpu": "512",
"memory": "1024",
"executionRoleArn": "arn:aws:iam::...:role/ecsTaskExecutionRole",
"taskRoleArn": "arn:aws:iam::...:role/taskflowAppRole",
"containerDefinitions": [{
"name": "api",
"image": "<account>.dkr.ecr.<region>.amazonaws.com/taskflow:<git-sha>",
"portMappings": [{ "containerPort": 3000 }],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/taskflow-api",
"awslogs-region": "us-east-1",
"awslogs-stream-prefix": "api"
}
}
}]
}Two IAM roles tripped me up until I separated their jobs:
- Execution role - permissions ECS itself needs to start the task: pull the image from ECR, write logs to CloudWatch.
- Task role - permissions my application code needs at runtime: e.g. read/write the S3 bucket from my last post. This is how my container gets AWS credentials without me ever putting access keys in env vars - the SDK picks them up automatically from the task role. That clean separation is one of my favorite things I've learned in AWS so far.
Note the image is tagged by git SHA - exactly the convention from my CI/CD post, now paying off.
Fargate vs. EC2 launch types
ECS can run tasks two ways:
- EC2 launch type - you provision and manage a fleet of EC2 instances; ECS packs tasks onto them. More control, cheaper at steady high scale, but you patch and scale the hosts.
- Fargate - serverless. You specify CPU/memory per task and AWS runs it; there are no instances to see or manage.
I chose Fargate. As a solo developer, not babysitting a host fleet is worth the premium, and it fits the stateless model - tasks are disposable, state lives in Postgres/Redis/S3.
Networking: awsvpc mode
In awsvpc mode, each task gets its own Elastic Network Interface with its own private IP inside my VPC - like its own little machine on the network. That makes security groups apply per task, which is exactly the isolation I want. (The VPC/subnet/security-group details are deep enough that they're my next post.)
Wiring up the load balancer
Tasks are ephemeral - they come and go with their own IPs - so I can't hardcode where they are. An Application Load Balancer (ALB) solves this:
- The ECS service registers each task into a target group.
- The ALB health-checks
/health; only healthy tasks receive traffic. - During a deploy, the service spins up new tasks, waits for them to pass health checks, registers them, then drains the old ones - a rolling deploy with zero downtime.
This is the load-balancing theory from my scaling post, now concrete: the ALB is the stable front door, tasks scale behind it, and Service Auto Scaling adds/removes tasks based on CPU or request count.
What's clicking and what's still hard
Clicking: how cleanly ECS composes everything - my Docker image, IAM least-privilege via the task role, the load balancer, and rolling deploys all fit together.
Still hard: the networking underneath. Public vs. private subnets, why my task couldn't reach the internet to pull the image at first (it needed a NAT gateway / proper subnet routing), and security-group rules. That confusion is exactly what I'm tackling next.
Takeaways
- Cluster → service → task → task definition is the whole mental model.
- Two roles, two jobs: execution role to start the task, task role for the app's AWS access.
- Fargate trades cost for not managing hosts - usually worth it early.
- The ALB + health checks are what make ephemeral tasks safe to deploy continuously.
Next
ECS made it obvious I don't actually understand the network my containers live in. Next I'm going one layer down: AWS IAM, VPCs, subnets, and security groups - the security and networking foundation everything else sits on.