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 mode | How it gives IPs | Works on Fargate? | Works on EC2? | When to use |
|---|---|---|---|---|
awsvpc | Each task gets its own ENI and private IP | Yes (only option) | Yes | Default for new workloads; gives per-task security groups and clean networking |
bridge | Tasks share the host via a Docker virtual network | No | Yes | Older EC2 workloads; needs dynamic port mapping |
host | Task uses the EC2 instance’s network directly | No | Yes | Rare; maximum performance, no port isolation |
none | No external networking | No | Yes | Batch 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
- Open the EC2 console, go to Target Groups, and choose Create target group.
- Pick target type IP addresses, set the protocol to HTTP and the port to your container port (e.g.
8080). - Select your VPC and set a health check path like
/health. Create the group — do not register targets manually; ECS does that. - Create or open an Application Load Balancer with a listener on port
443that forwards to this target group. - 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 anyawsvpcor Fargate service, the target type must beip.
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
/24subnet 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.largesupports a handful). Pack too manyawsvpctasks onto one instance and you hit the ENI cap before you run out of CPU or memory.
Plan ahead: Size subnets generously — prefer
/20or larger (4,000+ IPs) for busy container subnets, and spread tasks across multiple subnets in different Availability Zones. On EC2, enable ENI trunking (theawsvpcTrunkingaccount setting) to raise the ENI ceiling, or just use Fargate so the limit is not your problem.
Best practices
- Use
awsvpcmode 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
awsvpctasks — instance-type targets will not register. - Size container subnets large (
/20or 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.