AWS for DevOps — ECS Task Definitions
7.3 — ECS Task Definitions
A task definition is the blueprint for your containers on ECS. Think of it as a docker-compose.yml for AWS — it defines what image to run, CPU/memory, ports, environment variables, logging, and IAM roles.
Anatomy of a Task Definition
Task Definition (my-app:5)
┌─────────────────────────────────┐
│ Task Role: arn:...:role/app │ ◀── permissions for your app code
│ Execution Role: arn:...:role/e │ ◀── permissions for ECS agent
│ Network Mode: awsvpc │
│ CPU: 512 Memory: 1024 │
│ │
│ Container Definitions: │
│ ┌───────────────────────┐ │
│ │ Container: app │ │
│ │ Image: ECR/my-app:v1 │ │
│ │ Port: 8080 │ │
│ │ CPU: 256 Memory: 512 │ │
│ │ Logs: CloudWatch │ │
│ └───────────────────────┘ │
│ ┌───────────────────────┐ │
│ │ Container: sidecar │ │
│ │ Image: ECR/envoy:v1 │ │
│ │ Port: 9901 │ │
│ │ CPU: 256 Memory: 512 │ │
│ └───────────────────────┘ │
└─────────────────────────────────┘
Registering a Task Definition (CLI)
$ aws ecs register-task-definition --cli-input-json file://task-def.json
task-def.json:
{
"family": "my-app",
"networkMode": "awsvpc",
"requiresCompatibilities": ["FARGATE"],
"cpu": "512",
"memory": "1024",
"executionRoleArn": "arn:aws:iam::123456789012:role/ecsTaskExecutionRole",
"taskRoleArn": "arn:aws:iam::123456789012:role/myAppTaskRole",
"containerDefinitions": [
{
"name": "app",
"image": "123456789012.dkr.ecr.ap-south-1.amazonaws.com/my-app:v1",
"essential": true,
"portMappings": [
{
"containerPort": 8080,
"protocol": "tcp"
}
],
"environment": [
{ "name": "APP_ENV", "value": "production" },
{ "name": "LOG_LEVEL", "value": "info" }
],
"secrets": [
{
"name": "DB_PASSWORD",
"valueFrom": "arn:aws:ssm:ap-south-1:123456789012:parameter/app/db-password"
},
{
"name": "API_KEY",
"valueFrom": "arn:aws:secretsmanager:ap-south-1:123456789012:secret:app/api-key-AbCdEf"
}
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/my-app",
"awslogs-region": "ap-south-1",
"awslogs-stream-prefix": "app"
}
},
"healthCheck": {
"command": ["CMD-SHELL", "curl -f http://localhost:8080/health || exit 1"],
"interval": 30,
"timeout": 5,
"retries": 3,
"startPeriod": 60
}
}
]
}
Key Fields Explained
| Field | Purpose | Example |
|---|---|---|
family |
Logical name — revisions auto-increment |
my-app → my-app:1, my-app:2
|
networkMode |
Networking — awsvpc for Fargate |
awsvpc gives each task its own ENI |
cpu / memory
|
Task-level resources |
"512" = 0.5 vCPU, "1024" = 1 GB |
executionRoleArn |
ECS agent permissions | Pull images from ECR, write CloudWatch Logs |
taskRoleArn |
Application permissions | Read S3, call DynamoDB, send SQS messages |
essential |
If true and container stops, task stops |
Main app = true, sidecars = depends |
Fargate CPU / Memory Combinations
| CPU (vCPU) | Memory Options (GB) |
|---|---|
| 0.25 | 0.5, 1, 2 |
| 0.5 | 1, 2, 3, 4 |
| 1 | 2, 3, 4, 5, 6, 7, 8 |
| 2 | 4–16 (in 1 GB increments) |
| 4 | 8–30 (in 1 GB increments) |
Invalid combinations cause registration errors. Example:
cpu=256, memory=8192is invalid.
Two IAM Roles — Don’t Confuse Them
| Role | Trust | Who Uses It | Permissions |
|---|---|---|---|
| Execution Role | ecs-tasks.amazonaws.com |
ECS agent | Pull ECR images, write CloudWatch Logs, read SSM/Secrets Manager |
| Task Role | ecs-tasks.amazonaws.com |
Your application code | Whatever your app needs (S3, DynamoDB, SQS, etc.) |
┌────────────────────────────┐
│ ECS Task │
│ │
│ Execution Role │
│ (used by ECS agent) │
│ - ecr:GetAuthorizationToken│
│ - ecr:GetDownloadUrlFor... │
│ - logs:CreateLogStream │
│ - ssm:GetParameters │
│ │
│ Task Role │
│ (used by YOUR code) │
│ - s3:GetObject │
│ - dynamodb:PutItem │
│ - sqs:SendMessage │
└────────────────────────────┘
Secrets Injection
| Source |
valueFrom Format |
|---|---|
| SSM Parameter Store | arn:aws:ssm:REGION:ACCOUNT:parameter/path/to/param |
| Secrets Manager | arn:aws:secretsmanager:REGION:ACCOUNT:secret:name-AbCdEf |
| Secrets Manager (specific key) | arn:aws:secretsmanager:REGION:ACCOUNT:secret:name-AbCdEf:key:: |
Secrets are injected as environment variables at container startup — your app reads them like any env var.
Logging Configuration
{
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/my-app",
"awslogs-region": "ap-south-1",
"awslogs-stream-prefix": "app",
"awslogs-create-group": "true"
}
}
}
Log streams follow the pattern: prefix/container-name/task-id
/ecs/my-app
└── app/app/abc123-def456-...
└── app/app/ghi789-jkl012-...
Updating a Task Definition
Task definitions are immutable. To update, register a new revision:
# List current revisions
$ aws ecs list-task-definitions --family-prefix my-app
{
"taskDefinitionArns": [
"arn:aws:ecs:ap-south-1:123456789012:task-definition/my-app:1",
"arn:aws:ecs:ap-south-1:123456789012:task-definition/my-app:2",
"arn:aws:ecs:ap-south-1:123456789012:task-definition/my-app:3"
]
}
# Register a new revision (change the image tag)
# Edit task-def.json with new image tag, then:
$ aws ecs register-task-definition --cli-input-json file://task-def.json
# Creates my-app:4
Common Mistakes
| Mistake | Why It Hurts | Fix |
|---|---|---|
| Confusing Execution Role vs Task Role | App can’t access AWS services (or ECS can’t pull images) | Execution Role → ECS agent; Task Role → your app |
| Invalid CPU/memory combo | Task registration fails | Check the Fargate combination table |
Hard-coding secrets in environment
|
Secrets in plaintext in task def | Use secrets with SSM or Secrets Manager |
| No health check | ECS can’t detect unhealthy containers | Add healthCheck with proper startPeriod
|
Quick Reference
| Task | Command |
|---|---|
| Register task def | aws ecs register-task-definition --cli-input-json file://task-def.json |
| Describe task def | aws ecs describe-task-definition --task-definition NAME:REVISION |
| List families | aws ecs list-task-definition-families |
| List revisions | aws ecs list-task-definitions --family-prefix NAME |
| Deregister | aws ecs deregister-task-definition --task-definition NAME:REVISION |
Next → ECS Services & Scaling