AWS ECS Fargate containers

ECS Fargate task stopped unexpectedly: diagnosing container exits

Fix ECS Fargate tasks that keep stopping with Essential container exited, OutOfMemoryError, or CannotPullContainerError by identifying the root cause in stopped task metadata and CloudWatch logs.

·
ECS tasks keep stopping and restarting. The service appears healthy in the console, but users experience intermittent errors. This runbook shows how to identify why tasks are being killed and fix the most common causes.

This runbook covers the most frequent ECS Fargate task failures. For ongoing container management, see CloudOps Maintenance. For a real-world migration example, read our EC2 to ECS with Fargate case study.

Symptoms

Tasks are being stopped and replaced continuously. The ECS service events show:

# Check stopped task reason
aws ecs describe-tasks \
  --cluster my-cluster \
  --tasks $(aws ecs list-tasks --cluster my-cluster \
    --service-name my-service --desired-status STOPPED \
    --query 'taskArns[0]' --output text) \
  --query 'tasks[0].{reason:stoppedReason,status:lastStatus,stopCode:stopCode}'

# Common outputs:
# "Essential container in task exited"
# "Task stopped by: OutOfMemoryError: Container killed due to memory usage"
# "CannotPullContainerError: pull image manifest has been retried 5 time(s)"

# Check service events for patterns
aws ecs describe-services \
  --cluster my-cluster \
  --services my-service \
  --query 'services[0].events[:10].[createdAt,message]' \
  --output table

You may also see the service oscillating between desired count and zero running tasks, or a high “stopped tasks” count in the ECS console.

Cause

ECS Fargate tasks stop for three primary reasons:

1. Out of Memory (OOMKill): The container exceeds its configured memory hard limit. Fargate kills the container immediately with no graceful shutdown. This is common when memory limits are set too close to actual usage, or when the application has memory leaks.

2. Essential container exited: The main container process crashed (non-zero exit code) or exited cleanly (exit code 0 but the task definition expects it to run indefinitely). Often caused by unhandled exceptions, missing environment variables, or failed health checks that trigger container replacement.

3. CannotPullContainerError: Fargate cannot pull the container image from the registry. This happens when ECR permissions are misconfigured, the image tag does not exist, or the VPC lacks a NAT Gateway / VPC endpoint for ECR access.

Fix

A) Out of Memory: increase memory limits:

# Check actual memory usage before the kill
aws logs filter-log-events \
  --log-group-name /ecs/my-service \
  --filter-pattern "memory" \
  --start-time $(date -d '1 hour ago' +%s000) \
  --query 'events[*].message'

# Check Container Insights for peak memory (if enabled)
aws cloudwatch get-metric-statistics \
  --namespace ECS/ContainerInsights \
  --metric-name MemoryUtilized \
  --dimensions Name=ServiceName,Value=my-service \
               Name=ClusterName,Value=my-cluster \
  --start-time $(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%S) \
  --end-time $(date -u +%Y-%m-%dT%H:%M:%S) \
  --period 60 --statistics Maximum \
  --query 'Datapoints | sort_by(@, &Timestamp) | [-5:]'

# Update task definition with higher memory
# Rule of thumb: set hard limit to 1.5x observed peak
aws ecs register-task-definition \
  --cli-input-json file://task-definition.json
# Then update the service to use the new task definition revision
aws ecs update-service \
  --cluster my-cluster \
  --service my-service \
  --task-definition my-service:NEW_REVISION

B) Essential container exited: check logs and health checks:

# Get the exit code of the stopped container
aws ecs describe-tasks \
  --cluster my-cluster \
  --tasks <TASK_ARN> \
  --query 'tasks[0].containers[*].{name:name,exitCode:exitCode,reason:reason}'

# Check CloudWatch logs for crash reason
aws logs tail /ecs/my-service --since 30m --format short | tail -50

# If health check is killing the container, review the health check config:
# - Ensure the health check endpoint returns 200 within the timeout
# - Increase startPeriod for slow-starting applications (default: 0s)
# - Common fix: set startPeriod to 60-120s for Java/Spring apps

# Check environment variables are properly set
aws ecs describe-task-definition \
  --task-definition my-service \
  --query 'taskDefinition.containerDefinitions[0].environment'

C) CannotPullContainerError: fix image access:

# Verify the image exists in ECR
aws ecr describe-images \
  --repository-name my-service \
  --image-ids imageTag=latest \
  --query 'imageDetails[0].imagePushedAt'

# Check task execution role has ECR permissions
aws iam simulate-principal-policy \
  --policy-source-arn arn:aws:iam::123456789012:role/ecsTaskExecutionRole \
  --action-names ecr:GetDownloadUrlForLayer ecr:BatchGetImage \
  --query 'EvaluationResults[*].{Action:EvalActionName,Decision:EvalDecision}'

# If using private subnets, verify VPC endpoints or NAT Gateway exist
aws ec2 describe-vpc-endpoints \
  --filters "Name=service-name,Values=com.amazonaws.eu-west-1.ecr.dkr" \
  --query 'VpcEndpoints[*].{VpcId:VpcId,State:State}'

Prevention: Set memory soft limit (memoryReservation) to actual expected usage and hard limit to 1.5x peak. Configure health check startPeriod generously for slow-starting applications. Pin image tags to specific digests in production rather than using latest.

Verification

# 1. Check tasks are running and stable
aws ecs describe-services \
  --cluster my-cluster \
  --services my-service \
  --query 'services[0].{running:runningCount,desired:desiredCount,pending:pendingCount}'
# Expected: running == desired, pending == 0

# 2. Verify no recent task stops
aws ecs list-tasks --cluster my-cluster \
  --service-name my-service --desired-status STOPPED \
  --query 'taskArns | length(@)'
# Expected: 0 or only old tasks

# 3. Check service events for stability (no recent restarts)
aws ecs describe-services \
  --cluster my-cluster \
  --services my-service \
  --query 'services[0].events[:5].message'
# Expected: "has reached a steady state" message

# 4. Verify health check passing
aws elbv2 describe-target-health \
  --target-group-arn <TARGET_GROUP_ARN> \
  --query 'TargetHealthDescriptions[*].{Target:Target.Id,Health:TargetHealth.State}'
# Expected: all targets "healthy"

If the service maintains steady state for 10+ minutes with no stopped tasks, the issue is resolved. Monitor CloudWatch alarms for task count drops over the next 24 hours.

Every stopped task means degraded service for your users. ECS silently replaces failed tasks, which masks the problem: your service appears “running” while users experience intermittent 502 errors and timeouts. Without proper health checks and alerting on task stop events, these failures can persist for days before anyone notices the pattern.

 

Jerzy Kopaczewski

ECS tasks crashing in production?

Book a free 30-minute call. We'll review your task definitions and health check configuration to stabilise your services.