← All articles
AWSECS FargateContainersTroubleshooting

Fix: ECS Fargate Task Stuck in PENDING — the 5 Real Causes

An ECS Fargate task stuck in PENDING almost always comes down to one of five things. Here's the diagnostic command to run first, and how to fix each cause.

An ECS Fargate task stuck in PENDING is almost always a networking or IAM problem during the image pull — not a problem with your container. The task never starts, so there are no application logs to read, which is what makes it feel unfixable. Five root causes account for nearly all of it: no route to ECR, a missing public IP, an under-permissioned execution role, subnet IP exhaustion, and blocked egress.

Start with the diagnostic command below. It tells you which of the five you're hitting, so you don't have to guess.

Run this first

PENDING itself carries no detail. The useful information is in the stopped reason, and it only appears once the task transitions out:

aws ecs describe-tasks \
  --cluster my-cluster \
  --tasks <task-id> \
  --query "tasks[0].{last:lastStatus,desired:desiredStatus,reason:stoppedReason,containers:containers[].reason}"

If the task is still sitting in PENDING and hasn't stopped yet, watch the service events instead — they narrate what ECS is attempting:

aws ecs describe-services \
  --cluster my-cluster \
  --services my-service \
  --query "services[0].events[0:10].[createdAt,message]" \
  --output table
💡

A task that stays in PENDING for more than about 60 seconds is stuck, not slow. Fargate image pulls for reasonably sized images complete in seconds. If you're past a minute, stop waiting and start reading the stopped reason.

Map the reason string you get to the sections below.

1. The task can't reach ECR

Symptom: CannotPullContainerError, often with dial tcp ... i/o timeout or context deadline exceeded.

This is the most common cause by a wide margin. A Fargate task in a private subnet has no route to the internet by default, and pulling an image needs more endpoints than most people expect.

Fargate needs to reach four things to start a task:

EndpointWhy
com.amazonaws.<region>.ecr.apiAuthentication and image metadata
com.amazonaws.<region>.ecr.dkrThe Docker registry API itself
com.amazonaws.<region>.s3 (gateway)ECR stores the actual layers in S3
com.amazonaws.<region>.logsThe awslogs driver, before the container starts

The S3 one catches almost everyone. ECR's API and registry endpoints are interface endpoints, but the layers live in S3 — so a gateway endpoint is required, and it attaches to a route table rather than a subnet.

# Interface endpoints (one ENI per subnet, needs a security group)
aws ec2 create-vpc-endpoint \
  --vpc-id vpc-abc123 \
  --vpc-endpoint-type Interface \
  --service-name com.amazonaws.us-east-1.ecr.dkr \
  --subnet-ids subnet-aaa subnet-bbb \
  --security-group-ids sg-endpoints \
  --private-dns-enabled

# Gateway endpoint for S3 — attaches to route tables, not subnets
aws ec2 create-vpc-endpoint \
  --vpc-id vpc-abc123 \
  --vpc-endpoint-type Gateway \
  --service-name com.amazonaws.us-east-1.s3 \
  --route-table-ids rtb-private

Two things to check on the interface endpoints:

  • --private-dns-enabled must be on. Without it, ecr.dkr resolves to the public endpoint and the pull goes nowhere.
  • The endpoint's own security group must allow inbound 443 from the task's security group. An endpoint ENI is a network device with its own rules; if it rejects the connection, the pull times out exactly as if the endpoint didn't exist.

The alternative to endpoints is a NAT gateway. It's fewer moving parts, but it bills per hour and per GB, and image pulls are not small. For anything long-lived, endpoints are usually cheaper.

2. A public subnet with no public IP

Symptom: CannotPullContainerError with a timeout, on a subnet you know has an internet gateway.

A Fargate task in a public subnet still needs a public IP to use that internet gateway. There's no NAT in front of it. Miss the flag and the pull stalls with no clear message:

aws ecs run-task \
  --cluster my-cluster \
  --task-definition my-task \
  --launch-type FARGATE \
  --network-configuration "awsvpcConfiguration={subnets=[subnet-public-a],securityGroups=[sg-task],assignPublicIp=ENABLED}"

In a service definition, the same setting is assignPublicIp under networkConfiguration.awsvpcConfiguration. In CloudFormation and Terraform it defaults to DISABLED — so it's an easy one to lose when moving from a console prototype to infrastructure as code.

⚠️

assignPublicIp=ENABLED on a private subnet does nothing useful. The task gets an IP that isn't routable, and you're back to needing a NAT gateway or VPC endpoints. Match the setting to the subnet.

3. The execution role is missing permissions

Symptom: CannotPullContainerError mentioning authorization, or ResourceInitializationError: unable to pull secrets or registry auth.

ECS uses two different roles, and mixing them up is common:

  • Task execution role — used by the Fargate agent before your container runs. Pulls the image, fetches secrets, creates log streams.
  • Task role — used by your application code once it's running, to call AWS APIs.

A PENDING task has not reached your code yet, so the problem is essentially always the execution role. It needs, at minimum:

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": [
      "ecr:GetAuthorizationToken",
      "ecr:BatchCheckLayerAvailability",
      "ecr:GetDownloadUrlForLayer",
      "ecr:BatchGetImage",
      "logs:CreateLogStream",
      "logs:PutLogEvents"
    ],
    "Resource": "*"
  }]
}

The managed policy AmazonECSTaskExecutionRolePolicy covers all of this.

If the task definition references secrets from Secrets Manager or SSM Parameter Store, the execution role also needs secretsmanager:GetSecretValue or ssm:GetParametersand a route to those services. This produces ResourceInitializationError: unable to pull secrets or registry auth, which reads like an image problem but is usually a missing com.amazonaws.<region>.secretsmanager endpoint.

Also check the role's trust policy. It must allow ecs-tasks.amazonaws.com to assume it:

aws iam get-role --role-name ecsTaskExecutionRole \
  --query "Role.AssumeRolePolicyDocument"

4. The subnet has no free IP addresses

Symptom: Long PENDING with no stopped reason at all, or service events mentioning ENI attachment failures.

Fargate uses awsvpc networking, which means every task gets its own ENI and consumes a subnet IP address. A /28 subnet has 16 addresses, 5 of which AWS reserves — so 11 tasks and you're full. Scaling a service is what usually exposes this.

aws ec2 describe-subnets \
  --subnet-ids subnet-aaa subnet-bbb \
  --query "Subnets[].{id:SubnetId,cidr:CidrBlock,free:AvailableIpAddressCount}" \
  --output table

If free is at or near zero, spread the service across additional subnets or move to a larger CIDR. Note that tasks stopping and starting during a deployment temporarily need capacity for both the old and new tasks — a subnet that's merely tight will fail during a rolling deploy while looking fine at rest.

5. Security groups or NACLs block egress

Symptom: CannotPullContainerError with a timeout, when endpoints and IAM both look correct.

The task's security group needs outbound 443. Security groups allow all egress by default, but hardened setups often replace that rule and forget that the image pull itself needs to get out:

aws ec2 describe-security-groups --group-ids sg-task \
  --query "SecurityGroups[0].IpPermissionsEgress"

NACLs are the subtler one. They're stateless, so allowing outbound 443 is not enough — the return traffic arrives on an ephemeral port, and the inbound rule has to allow ports 1024–65535 or the response is dropped. The pull then hangs rather than failing cleanly, which is why NACLs are usually the last thing anyone checks.

Common errors and what they actually mean

Error stringReal cause
CannotPullContainerError: ... i/o timeoutNo route to ECR — missing VPC endpoint, NAT, or public IP (causes 1, 2, 5)
CannotPullContainerError: ... 403 ForbiddenExecution role lacks ECR permissions (cause 3)
CannotPullContainerError: ... manifest unknownThe image tag genuinely doesn't exist in the repository
ResourceInitializationError: unable to pull secrets or registry authMissing Secrets Manager/SSM permissions, or no route to those endpoints (cause 3)
ResourceInitializationError: failed to configure ENISubnet IP exhaustion (cause 4)
Timeout waiting for network interface provisioningSubnet or ENI capacity (cause 4)

Verify the image tag exists before blaming the network — it takes seconds and rules out an entire category:

aws ecr describe-images \
  --repository-name my-service \
  --image-ids imageTag=latest

The order that saves time

  1. Read stoppedReason. It usually names the cause outright.
  2. Confirm the image tag exists in ECR.
  3. Check the subnet: public with assignPublicIp=ENABLED, or private with all four endpoints including the S3 gateway.
  4. Check the execution role, not the task role.
  5. Check free IPs in the subnet.
  6. Check SG egress, then NACL return traffic.

Most people lose an afternoon here because they debug the container. The container is fine — it never ran.

Build the mental model once

Nearly every PENDING problem is a VPC problem wearing a container costume, and it stops being mysterious once you've stood up a Fargate service end to end and seen which piece of wiring each failure corresponds to.

The ECS Fargate workshop does exactly that — VPC, subnets, endpoints, roles and a running service, in one sitting. If you're still deciding whether Fargate is the right target at all, the comparison in Fargate vs. EKS vs. Lambda covers where each one fits. More container troubleshooting write-ups are collected under ECS Fargate.

Keep reading