ECS is forgiving to start with and unforgiving to run carelessly. An oversized task bills every second, a deployment with no safety net can loop on a broken image, a plaintext secret leaks into CloudTrail, and forgotten services quietly accrue cost.
Walk this list once now, then use it as a review checklist before shipping any code that registers, runs, deploys, or tears down ECS tasks and services.
Pick a valid Fargate CPU/memory pair. Each CPU value permits only a specific memory range; an invalid pair is rejected at registration, so size deliberately.
Right-size to real usage plus headroom. Fargate bills per vCPU-second and GB-second, so an oversized task wastes money every second it runs.
Separate the execution role from the task role. The execution role pulls images, writes logs, and reads secrets; the task role is your app's least-privilege runtime identity.
Treat task definitions as immutable revisions. Never try to edit one; register a new revision and deploy that.
Set a container stop timeout you can meet. Handle SIGTERM and finish within stopTimeout so graceful stops actually complete.
Register with the two distinct roles and a valid size pair.
Enable the deployment circuit breaker with rollback. A broken image otherwise loops forever launching crashing tasks; the breaker fails the deploy and reverts.
Set a health-check grace period. Give slow-starting tasks time to boot so the load balancer does not deregister them before they are ready.
Keep minimumHealthyPercent at 100 in production. A surge-style rollout never drops below current capacity; 0 risks an outage mid-deploy.
Wait for services to stabilize. Use the services_stable waiter (or poll rolloutState) so your pipeline sees a real pass or fail, not fire-and-forget.
Use forceNewDeployment for same-tag rebuilds. If the task definition did not change, force a redeploy to pick up a rebuilt image.
Deploy with the circuit breaker on and wait for the result.
Standardize on the awslogs driver. Ship every container's stdout/stderr to a named CloudWatch log group with a stream prefix.
Blend Fargate and Fargate Spot. A capacity provider strategy runs interruptible work on Spot at a large discount with a small on-demand base for resilience.
Auto scale services on real metrics. Application Auto Scaling target-tracking on CPU or request count matches capacity to demand instead of paying for idle tasks.
Wait on transitions with waiters. Use tasks_running/tasks_stopped and services_stable rather than hand-rolled sleeps.
Handle SIGTERM for Spot and deploys. Drain and checkpoint within two minutes so reclaimed or replaced tasks lose no in-flight work.
Choose a valid CPU/memory pair (each CPU value allows only a set memory range), sized to the container's real footprint plus headroom. Oversizing bills per vCPU-second and GB-second for capacity you never use.
What is the single most important deployment setting?
The deployment circuit breaker with rollback: true. It detects a deployment whose tasks keep failing and automatically reverts to the last working revision instead of looping on a broken image.
Why should the task role and execution role be different?
They serve different phases. The execution role pulls images, writes logs, and reads secrets before your app runs. The task role is your application's runtime identity and should be least-privilege to limit blast radius.
How do I stop the load balancer from killing slow tasks?
Set healthCheckGracePeriodSeconds above your worst-case cold start. ECS then ignores load balancer health checks during warm-up so a booting task is not deregistered before it can serve.
What is the safest way to pass secrets to a container?
The secrets field referencing a Secrets Manager or SSM ARN via valueFrom. The value is injected at startup and never appears in the task definition, the API, or CloudTrail, unlike a plaintext env var.
How do I cut Fargate cost?
Right-size tasks, auto scale services to demand, and use a capacity provider strategy that runs interruptible work on Fargate Spot with a small on-demand base for resilience.
Which log driver should I use?
awslogs for most workloads - it ships stdout/stderr to a CloudWatch log group you name. Standardizing on it gives every service consistent, queryable logs without extra infrastructure.
How do I delete a service cleanly?
Scale it to zero with UpdateService (desiredCount: 0), wait for it to stabilize, then call DeleteService. Deleting a service with running tasks is blocked, so drain first.
Should I write my own retry logic for ECS calls?
Usually no. Both SDKs retry throttling and transient errors with backoff. Tune retry mode and max attempts rather than reimplementing it, and log the request id on failure for support.
How do I wait for a deployment or task instead of sleeping?
Use waiters: tasks_running/tasks_stopped for tasks and services_stable for deployments in boto3, or waitUntilTasksRunning/waitUntilServicesStable in SDK v3. They poll for you until the target state or a timeout.