The image built clean. docker run on your laptop worked on the first try. You pushed to ECR, updated the task definition, and the ECS service just won't stabilize. Tasks start, run for eleven seconds, and die. The service keeps launching replacements, burning through your desired count, and the deployment circuit breaker eventually gives up and rolls back. You're staring at a blank CloudWatch log group wondering if the container even started.
This exact scenario burns hours for almost every team moving to Fargate for the first time, and it's almost never a Docker problem. It's an ECS configuration problem wearing a Docker costume.
The Fix #
Work through this in order — it covers most "works locally, dies on Fargate" cases.
Check your image architecture first. If you're on an Apple Silicon Mac, docker build produces an ARM64 image by default. Fargate's default runtime is x86_64. Build explicitly for the right platform:
docker buildx build --platform linux/amd64 -t myapp:latest --push .
Or, if you want ARM64 for the cost savings, declare it explicitly in your task definition so ECS launches on Graviton-backed Fargate:
"runtimePlatform": {
"cpuArchitecture": "ARM64",
"operatingSystemFamily": "LINUX"
}
Just make sure both sides agree.
Turn on logging before you debug anything else. If your task definition doesn't have the awslogs driver configured, you're debugging blind:
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/myapp",
"awslogs-region": "us-east-1",
"awslogs-stream-prefix": "myapp"
}
}
Create the log group first — a missing log group can itself cause the task to fail.
Check networking before you assume it's the app. A Fargate task in a private subnet needs a route to a NAT Gateway to pull the image from ECR and reach other AWS APIs, unless you're using VPC endpoints for ECR/S3. No route means the task gets stuck PROVISIONING or fails silently trying to pull the image.
Separate execution role from task role. The execution role is what ECS uses to pull your image and write logs. The task role is what your application code uses to call AWS APIs at runtime. Confusing these is the single most common IAM mistake in ECS — your app failing to reach S3 or DynamoDB is a task role problem, not an execution role problem.
Use ECS Exec for live debugging instead of guessing:
aws ecs execute-command \
--cluster my-cluster \
--task <task-id> \
--container myapp \
--interactive \
--command "/bin/sh"
The Gotchas #
- Exit code 137 means OOM-killed, not crashed. Your container isn't broken — it asked for more memory than the task definition allocated. Bump memory or profile what's actually eating it.
- Fargate CPU/memory combinations are fixed pairs, not a free slider. Request an invalid combination and the task fails at launch, before your app code even runs.
- Health check paths default to nothing meaningful. If your ALB target group health check hits
/and your app only serves/api/health, ECS will cycle tasks forever thinking they're unhealthy — even though the app is fine. - Task definition revisions pile up silently, and nobody notices until they're trying to figure out which of 40 revisions is actually running in prod. Tag or document your deploys.
- Fargate Spot saves real money but tasks can be reclaimed with a two-minute warning — don't put stateful or long-running batch work there without handling
SIGTERMgracefully.
TL;DR #
- Check image architecture (ARM vs x86) and logging configuration before you assume it's an app bug — most Fargate "crashes" are configuration, not code.
- Execution role pulls images and writes logs; task role is what your app uses at runtime — mixing these up causes confusing permission errors.
- Exit code 137 is a memory problem, not a mystery — check the task definition's memory allocation first.