Run a container on ECS Fargate
Run a container image on Amazon ECS with Fargate from the CLI or TypeScript — the two IAM roles and which one does what, a valid CPU/memory pair, a task definition with logs, the network route the task needs to pull its image, and how to read why a task stopped.
markdownguidev4GB9S-RJKFstage: buildtrust: officialservice: ecsinterface: cliinterface: sdkECS Fargaterun-taskregister-task-definitiontask execution roletask roleecs-tasks.amazonaws.comAmazonECSTaskExecutionRolePolicyawsvpcassignPublicIpFargate CPU memoryawslogsRunTaskCommanddescribe-tasksstoppedReason
**You need a task definition with a valid CPU/memory pair and an execution role, a cluster, and subnets whose route lets the task reach the image registry.** A missing route is the most common first failure: the task stops before your code starts.
1. Two roles, two jobs
| Role | Used by | Needs | Set as |
|---|---|---|---|
| Task execution role | The ECS agent and Fargate, before and around your container | Pull from ECR, write logs, fetch secrets for injection | executionRoleArn |
| Task role | Your application code | Whatever the app calls: S3, DynamoDB, … | taskRoleArn |
Both trust ecs-tasks.amazonaws.com. Start the execution role from the AWS managed AmazonECSTaskExecutionRolePolicy:
aws iam create-role --role-name ecsTaskExecutionRole \
--assume-role-policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Service":"ecs-tasks.amazonaws.com"},"Action":"sts:AssumeRole"}]}'
aws iam attach-role-policy --role-name ecsTaskExecutionRole \
--policy-arn arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
If the task definition injects secrets, add these to the execution role: secretsmanager:GetSecretValue for Secrets Manager, ssm:GetParameters for Parameter Store, and kms:Decrypt if a customer managed key encrypts them. An app that gets AccessDenied at runtime needs the permission on the task role instead. Whoever registers the task definition or runs the task needs iam:PassRole on both roles.
2. Pick CPU and memory from the table
Fargate accepts only fixed combinations (reference/ecs/roles-and-networking.md). For example, 256 CPU units take 512, 1024 or 2048 MiB, and 1024 units take 2048–8192 MiB. An invalid pair fails registration with Invalid 'cpu' setting for task (Terraform shows No Fargate configuration exists for given values).
3. Register the task definition
{
"family": "<app>",
"requiresCompatibilities": ["FARGATE"],
"networkMode": "awsvpc",
"cpu": "512",
"memory": "1024",
"runtimePlatform": { "cpuArchitecture": "X86_64", "operatingSystemFamily": "LINUX" },
"executionRoleArn": "arn:aws:iam::<account>:role/ecsTaskExecutionRole",
"taskRoleArn": "arn:aws:iam::<account>:role/<app>-task-role",
"containerDefinitions": [{
"name": "app",
"image": "<account>.dkr.ecr.<region>.amazonaws.com/<repo>:<tag>",
"essential": true,
"portMappings": [{ "containerPort": 8080 }],
"logConfiguration": {
"logDriver": "awslogs",
"options": { "awslogs-group": "/ecs/<app>", "awslogs-region": "<region>", "awslogs-stream-prefix": "app" }
}
}]
}
aws logs create-log-group --log-group-name /ecs/<app>
aws ecs register-task-definition --cli-input-json file://taskdef.json
aws ecs create-cluster --cluster-name <cluster>
Create the log group first. Otherwise the task fails at start with a ResourceInitializationError. The alternative is the log option awslogs-create-group: "true": it defaults to false, and it needs logs:CreateLogGroup in the execution role's policy (per the ECS API model's log-configuration documentation). The image architecture must match cpuArchitecture. Pin a tag or digest, not latest.
4. Choose the network path
Fargate tasks use awsvpc networking: each task gets its own network interface in your subnet. To pull an image, the task needs one of:
| Subnet | Setting | Works because |
|---|---|---|
| Private, route to a NAT gateway (zonal, or regional with no public subnet needed) | assignPublicIp=DISABLED | NAT reaches ECR |
| Private, no NAT | assignPublicIp=DISABLED + interface endpoints com.amazonaws.<region>.ecr.api and ecr.dkr + S3 gateway endpoint (+ logs and secretsmanager/ssm endpoints if used) | Image layers come from S3, so ECR endpoints alone are not enough (patterns/ecs-to-ecr.md) |
| Public, route to an internet gateway | assignPublicIp=ENABLED | The task's public IP reaches ECR |
A public subnet without a public IP, or a private subnet with neither NAT nor endpoints, fails to pull. You can attach up to 16 subnets and 5 security groups. Security groups need outbound HTTPS (443).
5. Run it
aws ecs run-task --cluster <cluster> --launch-type FARGATE --task-definition <app> \
--network-configuration 'awsvpcConfiguration={subnets=[<subnet>],securityGroups=[<sg>],assignPublicIp=DISABLED}'
import { ECSClient, RunTaskCommand, DescribeTasksCommand } from "@aws-sdk/client-ecs";
const ecs = new ECSClient({ region: "<region>" });
const run = await ecs.send(new RunTaskCommand({
cluster: "<cluster>",
taskDefinition: "<family>:<revision>",
launchType: "FARGATE",
count: 1,
networkConfiguration: {
awsvpcConfiguration: {
subnets: ["<subnet id>"],
securityGroups: ["<sg id>"],
assignPublicIp: "DISABLED", // ENABLED only in a public subnet with no NAT
},
},
}));
for (const f of run.failures ?? []) console.error(f.arn, f.reason, f.detail);
const arn = run.tasks?.[0]?.taskArn;
if (arn) {
const d = await ecs.send(new DescribeTasksCommand({ cluster: "<cluster>", tasks: [arn] }));
const t = d.tasks?.[0];
console.log(t?.lastStatus, t?.stoppedReason, t?.containers?.map((c) => [c.name, c.exitCode, c.reason]));
}
run-task can succeed as an API call yet return entries in failures. Always check them. For a long-running app behind a load balancer, use create-service instead. The target group's target type must be ip.
6. If the task stops
aws ecs describe-tasks --cluster <cluster> --tasks <task-id> \
--query 'tasks[0].{stopped:stoppedReason,code:stopCode,containers:containers[].[name,exitCode,reason]}'
Stopped tasks remain visible for about one hour, so look quickly or rely on the logs.
stoppedReasonstarts withResourceInitializationErrororCannotPullContainerError: the role or the network path (steps 1 and 4).- A container exit code: 137 means killed (out of memory or stop timeout), 139 a segfault, 255 an ENTRYPOINT/CMD failure.
Look the message up in reference/ecs/errors.md.