ECS Task Placement: Binpack, Spread, and When Each Costs You

3 min readCloud Infrastructure

ECS placement strategies are a cost vs. reliability dial. Choosing wrong means either wasted EC2 spend or higher blast radius during host failures.

awsecsplacementterraform

What placement strategies actually control

ECS placement strategies tell the scheduler where to start a new task within a cluster. They apply when a task launches — not continuously. If you change the strategy on a running service, existing tasks are not rebalanced. Only new task launches follow the updated strategy.

Three strategies are available: binpack, spread, and random. In practice, you choose between binpack and spread. Random is rarely the right answer.

ECS placement strategy semantics

ConceptContainer Orchestration

Placement strategies define a preference for where tasks land. They are best-effort, not guaranteed — if constraints or capacity force a different placement, ECS will override the strategy.

Prerequisites

  • ECS clusters and services
  • EC2 instance types and capacity

Key Points

  • Placement strategies are evaluated at task launch time, not continuously.
  • Strategies can be chained: ECS evaluates them in order until a host is selected.
  • Strategies interact with placement constraints — constraints are hard filters, strategies are soft preferences.
  • Fargate ignores placement strategies; they apply only to EC2 launch type.

Binpack: fewer instances, higher blast radius

Binpack packs tasks onto the fewest possible instances by maximizing utilization of a chosen resource (cpu or memory).

{
  "placementStrategy": [
    { "type": "binpack", "field": "memory" }
  ]
}

The benefit is cost: if your cluster runs 40 tasks that could fit on 4 instances instead of 8, you pay for 4. With EC2 pricing, that difference compounds across environments and AWS accounts.

The risk is blast radius. Fewer instances means more tasks per host. When a host terminates — scheduled replacement, capacity event, Spot interruption — more tasks die simultaneously. Recovery time for the service depends on how fast ECS can reschedule and how long each task takes to pass health checks.

Use binpack when:

  • Workloads are batch or async (failures can retry, not user-facing)
  • You run Spot instances and treat interruption as a normal event
  • Cost is the primary constraint and the workload can tolerate brief disruption

Spread: higher cost, better fault tolerance

Spread distributes tasks as evenly as possible across a specified dimension. The most common dimensions are instanceId (spread across hosts) and attribute:ecs.availability-zone (spread across AZs).

{
  "placementStrategy": [
    { "type": "spread", "field": "attribute:ecs.availability-zone" },
    { "type": "spread", "field": "instanceId" }
  ]
}

Chaining spread strategies like this is the standard pattern for user-facing services: first spread across AZs, then spread instances within each AZ. A single host failure takes down only the tasks on that host. A single AZ failure takes down only the tasks in that AZ.

Use spread when:

  • The service handles live user traffic
  • You have an SLA that requires surviving single-AZ failures
  • You run multiple environments (each env has its own instances, and spread within each matters)

Binpack vs Spread

The tradeoff is not technical correctness — both work. It is cost vs. resilience, and the right answer depends on what you are running.

Binpack
  • Minimizes instance count and EC2 spend
  • Higher task density per host increases blast radius on failure
  • Good for batch jobs, workers, and background processing
  • Requires Spot interruption handling if used with Spot instances
Spread
  • Maximizes fault isolation across hosts and AZs
  • Higher instance count and cost under the same task load
  • Required for any service with an availability SLA
  • Pairs well with ALB health checks and connection draining
Verdict

Default to spread for user-facing services. Use binpack for background workers and batch jobs. If cost is a concern on user-facing services, right-size instance types before switching to binpack.

Combining strategies with capacity providers

Real clusters often mix both approaches: On-Demand instances running spread for baseline capacity, Spot instances running binpack for burst workloads. ECS capacity providers enable this without managing two separate clusters.

{
  "capacityProviderStrategy": [
    { "capacityProvider": "FARGATE",      "weight": 1, "base": 2 },
    { "capacityProvider": "FARGATE_SPOT", "weight": 3, "base": 0 }
  ]
}

The base field reserves a minimum number of tasks on a specific provider — useful for ensuring at least some tasks run on On-Demand even during Spot shortages.

Terraform: one aws_ecs_cluster_capacity_providers resource per cluster

This is a common Terraform mistake. The aws_ecs_cluster_capacity_providers resource manages all capacity providers for a cluster as a single unit. If you define two separate aws_ecs_cluster_capacity_providers blocks targeting the same cluster, you will get:

ResourceInUseException: The specified capacity provider is in use and cannot be removed.

The fix is to list all providers in a single resource:

resource "aws_ecs_cluster_capacity_providers" "main" {
  cluster_name = aws_ecs_cluster.main.name

  capacity_providers = ["FARGATE", "FARGATE_SPOT"]

  default_capacity_provider_strategy {
    base              = 2
    weight            = 1
    capacity_provider = "FARGATE"
  }
}

Adding a second provider means editing this resource, not creating a new one.

Placement constraints: the hard filter before the strategy

Constraints run before strategies. If no instance satisfies the constraint, the task fails to place regardless of which strategy you specified.

The two constraint types are:

  • distinctInstance — each task must run on a different instance (useful for ensuring tasks do not co-locate)
  • memberOf — filter by a cluster query language expression, such as instance type, AZ, or custom attribute
{
  "placementConstraints": [
    {
      "type": "memberOf",
      "expression": "attribute:ecs.availability-zone in [us-east-1a, us-east-1b]"
    }
  ]
}
Constraints can cause silent placement failures

If a constraint eliminates all available instances, ECS will not start the task and will report it as pending. This is easy to miss during an incident: the service looks like it is scaling but task count never rises. Always validate constraint expressions in staging before applying to production, and set CloudWatch alarms on PendingTaskCount staying elevated.

A user-facing API service runs on ECS with 12 tasks across a cluster. During an AZ failure, all 12 tasks become unavailable. Which placement configuration would have prevented this?

medium

The cluster has EC2 instances in us-east-1a, us-east-1b, and us-east-1c. The service uses binpack on memory with no constraints.

  • ASwitch from binpack to random placement
    Incorrect.Random placement distributes tasks without AZ awareness. It may or may not spread across AZs depending on instance distribution, but provides no guarantee.
  • BAdd a spread strategy on attribute:ecs.availability-zone
    Correct!Spreading by AZ ensures tasks are distributed across all three AZs. An AZ failure takes down roughly one third of tasks, not all of them.
  • CIncrease the desired task count to 24
    Incorrect.More tasks help under binpack only if they land on different AZs, which binpack does not guarantee — it optimizes for host density, not AZ diversity.
  • DAdd a distinctInstance constraint
    Incorrect.distinctInstance prevents co-location on the same host, but does not enforce AZ distribution. All distinct instances could be in the same AZ.

Hint:The failure was AZ-level, not host-level. The strategy needs to spread across the AZ dimension specifically.