Skip to content
AWS aws containers 5 min read

Load Balancing & Networking for Containers

When you run containers on AWS, the hard part is rarely starting the container — it is letting traffic reach it. Each task or pod needs a way to receive requests from the internet (or from other services), and it needs to be found even though its IP address changes every time it restarts. This page explains how Amazon ECS (Elastic Container Service, AWS’s container orchestrator) assigns network addresses, how to wire tasks to a load balancer, and how service discovery lets services find each other by name. We will also cover the IP-exhaustion gotcha that bites teams at scale.

ECS network modes

A “network mode” decides how a container gets its IP address and how it shares the host’s network. ECS supports four modes, but in 2026 almost everyone uses awsvpc.

Network modeHow it gives IPsWorks on Fargate?Works on EC2?When to use
awsvpcEach task gets its own ENI and private IPYes (only option)YesDefault for new workloads; gives per-task security groups and clean networking
bridgeTasks share the host via a Docker virtual networkNoYesOlder EC2 workloads; needs dynamic port mapping
hostTask uses the EC2 instance’s network directlyNoYesRare; maximum performance, no port isolation
noneNo external networkingNoYesBatch jobs that never take traffic

An ENI is an “Elastic Network Interface” — a virtual network card attached to your task, with its own private IP inside your VPC (Virtual Private Cloud, your isolated network in AWS). In awsvpc mode every task behaves like its own little server: it gets a private IP, can have its own security group (a virtual firewall), and is reachable directly.

When to use awsvpc: essentially always for new services. It is the only mode Fargate supports, and it lets you apply security groups per task instead of per host.

When NOT to use it: if you are packing many tasks onto a single EC2 instance and you are short on IP addresses or ENIs, the dense bridge mode may fit more containers per host (more on this gotcha below).

Wiring a task to an Application Load Balancer

An Application Load Balancer (ALB) is a managed AWS service that receives HTTP/HTTPS traffic and spreads it across your healthy tasks. The ALB sends traffic to a “target group” — a list of backends plus a health check. ECS keeps that target group up to date as tasks come and go.

The key detail: a target group has a target type, and for awsvpc tasks (including all Fargate tasks) you must use target type ip, not instance. Instance targets register the EC2 instance’s port, which does not exist for a task that has its own ENI.

Console steps

  1. Open the EC2 console, go to Target Groups, and choose Create target group.
  2. Pick target type IP addresses, set the protocol to HTTP and the port to your container port (e.g. 8080).
  3. Select your VPC and set a health check path like /health. Create the group — do not register targets manually; ECS does that.
  4. Create or open an Application Load Balancer with a listener on port 443 that forwards to this target group.
  5. Open the ECS console, create or update your Service, and under Load balancing attach the ALB, choose the target group, and set the container name and port.

CLI equivalent

aws elbv2 create-target-group \
  --name web-tg \
  --target-type ip \
  --protocol HTTP --port 8080 \
  --vpc-id vpc-0a1b2c3d \
  --health-check-path /health

Output:

{
  "TargetGroups": [
    {
      "TargetGroupArn": "arn:aws:elasticloadbalancing:us-east-1:111122223333:targetgroup/web-tg/abc123",
      "TargetType": "ip",
      "Protocol": "HTTP",
      "Port": 8080
    }
  ]
}

Then attach it when creating the service:

aws ecs create-service \
  --cluster prod-cluster \
  --service-name web \
  --task-definition web:7 \
  --desired-count 3 \
  --launch-type FARGATE \
  --network-configuration "awsvpcConfiguration={subnets=[subnet-0a1b2c3d,subnet-0e4f5a6b],securityGroups=[sg-0a1b2c3d],assignPublicIp=DISABLED}" \
  --load-balancers "targetGroupArn=arn:aws:elasticloadbalancing:us-east-1:111122223333:targetgroup/web-tg/abc123,containerName=web,containerPort=8080"

Gotcha: If you accidentally create the target group with target type instance, the service will fail to stabilize and you will see registration errors. For any awsvpc or Fargate service, the target type must be ip.

Service discovery with AWS Cloud Map

Load balancers are for external (north-south) traffic. For services talking to each other (east-west), use AWS Cloud Map, a service-discovery system that maps a friendly DNS name to your task IPs. ECS registers each task automatically, so orders.internal always resolves to the current healthy orders tasks — no load balancer needed.

When to use this: internal microservice-to-microservice calls where you want a stable name instead of a hard-coded IP, and you do not need the extra features (or cost) of an ALB.

Create a namespace and use it

aws servicediscovery create-private-dns-namespace \
  --name internal --vpc vpc-0a1b2c3d

Then reference a Cloud Map service in your ECS service with --service-registries. Once registered, another task can simply call http://orders.internal:8080. There is a small monthly charge per Cloud Map service plus DNS query costs, but it is far cheaper than running an extra ALB for internal traffic.

The IP-exhaustion and ENI-limit gotcha

This is the issue that surprises teams as they grow. In awsvpc mode every task consumes one private IP from its subnet and one ENI. Two limits collide here:

  • Subnet IP space. A /24 subnet has only 251 usable IPs (AWS reserves 5). Three hundred tasks across that subnet simply will not fit, and new tasks fail to launch with an “insufficient IP addresses” error.
  • Per-instance ENI limits. On EC2 launch type, each instance type allows only so many ENIs (for example, an m5.large supports a handful). Pack too many awsvpc tasks onto one instance and you hit the ENI cap before you run out of CPU or memory.

Plan ahead: Size subnets generously — prefer /20 or larger (4,000+ IPs) for busy container subnets, and spread tasks across multiple subnets in different Availability Zones. On EC2, enable ENI trunking (the awsvpcTrunking account setting) to raise the ENI ceiling, or just use Fargate so the limit is not your problem.

Best practices

  • Use awsvpc mode for all new ECS services; it is required for Fargate and gives per-task security groups.
  • Always create IP-type target groups for Fargate and awsvpc tasks — instance-type targets will not register.
  • Size container subnets large (/20 or bigger) and span multiple Availability Zones to avoid IP exhaustion.
  • Put internal data plane traffic behind Cloud Map service discovery instead of paying for an extra ALB.
  • Keep tasks in private subnets with assignPublicIp=DISABLED; route outbound traffic through a NAT gateway and expose only the ALB publicly.
  • On EC2 launch type, enable ENI trunking before scaling dense task counts, or move to Fargate.
  • Set realistic health check paths and grace periods so the ALB does not kill tasks that are still warming up.
Last updated June 15, 2026
Was this helpful?