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:
| Endpoint | Why |
|---|---|
com.amazonaws.<region>.ecr.api | Authentication and image metadata |
com.amazonaws.<region>.ecr.dkr | The Docker registry API itself |
com.amazonaws.<region>.s3 (gateway) | ECR stores the actual layers in S3 |
com.amazonaws.<region>.logs | The 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-enabledmust be on. Without it,ecr.dkrresolves 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:GetParameters — and 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 string | Real cause |
|---|---|
CannotPullContainerError: ... i/o timeout | No route to ECR — missing VPC endpoint, NAT, or public IP (causes 1, 2, 5) |
CannotPullContainerError: ... 403 Forbidden | Execution role lacks ECR permissions (cause 3) |
CannotPullContainerError: ... manifest unknown | The image tag genuinely doesn't exist in the repository |
ResourceInitializationError: unable to pull secrets or registry auth | Missing Secrets Manager/SSM permissions, or no route to those endpoints (cause 3) |
ResourceInitializationError: failed to configure ENI | Subnet IP exhaustion (cause 4) |
Timeout waiting for network interface provisioning | Subnet 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
- Read
stoppedReason. It usually names the cause outright. - Confirm the image tag exists in ECR.
- Check the subnet: public with
assignPublicIp=ENABLED, or private with all four endpoints including the S3 gateway. - Check the execution role, not the task role.
- Check free IPs in the subnet.
- 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.