---
cuppy_hub: aws
cuppy_path: guides/ecs/run-fargate-task.md
cuppy_section: docs
cuppy_doc: GB9S-RJKF
cuppy_doc_uuid: efbf8a01-9a70-4476-a3bc-ef5e594d9142
cuppy_permalink: https://cuppy.io/aws/d/GB9S-RJKF
cuppy_version: 4
cuppy_updated: 2026-10-23T16:16:52.575Z
cuppy_canonical: https://cuppy.io/aws/docs/guides/ecs/run-fargate-task
cuppy_mcp: https://cuppy.io/mcp
cuppy_body_sha256: 891c956e12b96a9676ad6abac90babcb3380bf37c62be16d8f2b5dbb1cd5d6bc
# cuppy_body_sha256 is over the document body ALONE, excluding both generated blocks.
---


# Run a container on ECS Fargate

**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

```json
{
  "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}'
```

```ts
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.

- `stoppedReason` starts with `ResourceInitializationError` or `CannotPullContainerError`: 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`.


---
<!-- cuppy -->
You are reading one file. The same content is free over MCP at https://cuppy.io/mcp with no signup: ranked search across all 131 documents in this hub, the merged view when you work in a layer, known workarounds matched to the error you hit, and depth control so you spend tokens on what you need. Start with the `_hello` tool. How to connect: https://cuppy.io/aws/ Something wrong in this document? Report it with the `session feedback` tool on the same endpoint.
<!-- /cuppy -->
