ECS is a finicky beast when it comes to packing your tasks onto your container instances. You’ve got random and spread strategies, but they’re like throwing darts blindfolded. The real magic happens with binpack, which acts like a super-smart Tetris player for your containers, cramming them onto your instances with minimal wasted space.

Let’s see binpack in action. Imagine you have two container instances, i-0123456789abcdef0 and i-0123456789abcdef1, both with 4 vCPU and 8GB RAM.

Here’s a simple ECS service definition that uses binpack:

{
  "family": "my-app",
  "networkMode": "awsvpc",
  "containerDefinitions": [
    {
      "name": "my-app-container",
      "image": "nginx:latest",
      "cpu": 1024,
      "memory": 512,
      "portMappings": [
        {
          "containerPort": 80,
          "hostPort": 80
        }
      ]
    }
  ],
  "placementConstraints": [
    {
      "type": "memberOf",
      "expression": "ecs.cluster == my-cluster"
    }
  ],
  "placementStrategy": [
    {
      "type": "binpack",
      "field": "cpu"
    }
  ]
}

When you deploy this service with, say, 10 tasks, ECS won’t just randomly assign them. With binpack on cpu, it will try to fill up the first instance’s CPU capacity as much as possible before moving to the next. So, you’ll see tasks 1 through 4 (each needing 1 vCPU) land on i-0123456789abcdef0. Once that instance is full on CPU, tasks 5 through 8 will go onto i-0123456789abcdef1. If you had more tasks, and then decided to scale down to 6 tasks, ECS would try to consolidate those 6 tasks onto as few instances as possible, potentially moving tasks around to achieve maximum density.

The problem binpack solves is resource fragmentation. Without it, you might have many container instances that are only partially utilized, with enough free CPU or memory spread across them to run another task, but no single instance has enough contiguous capacity. This leads to over-provisioning and higher costs. binpack actively combats this by prioritizing filling up existing instances before launching new ones.

Internally, when ECS evaluates where to place a task using binpack, it iterates through your available container instances. For each instance, it calculates the remaining capacity for the field you specified (e.g., cpu or memory). It then selects the instance with the least remaining capacity for that field, meaning the instance that is already most utilized. This ensures that the most "full" instance gets the next task, effectively packing things tightly.

You can also binpack on memory. If you set "field": "memory", ECS will prioritize filling up the instance with the least available memory. This is useful if memory is your primary bottleneck. The key is to choose the field that represents your most constrained resource.

What many people miss is the interaction between binpack and task scaling. When you scale up, binpack works as described, filling existing instances. But when you scale down, ECS will also try to consolidate tasks onto fewer instances, potentially stopping and starting tasks on different instances to achieve the densest possible pack. This can lead to a temporary dip in availability if not managed carefully, as tasks are moved.

The next logical step after optimizing your placement strategy is to consider task scheduling with service discovery to automatically route traffic to your tightly packed tasks.

Want structured learning?

Take the full Ecs course →