How to Bind Docker Containers to Specific IP Addresses in Amazon ECS


1 views

When working with Amazon ECS (Elastic Container Service), many developers encounter a frustrating limitation compared to standard Docker deployments: the inability to bind containers to specific host IP addresses. This becomes particularly problematic when you need to:

  • Run multiple services on the same EC2 instance
  • Assign dedicated public IPs to each container
  • Maintain separate outbound IP identities
  • Host multiple SSL certificates on different IPs

In native Docker, we'd typically use:

docker run -p 203.0.113.5:80:8080 my-web-app

But ECS's abstraction layer removes this level of control. The task definition's portMappings only allows:

"portMappings": [
  {
    "containerPort": 8080,
    "hostPort": 80,
    "protocol": "tcp"
  }
]

Notice the missing hostIP parameter that exists in Docker's implementation.

After extensive testing, I've found these approaches work best:

1. Elastic Network Interfaces (ENI) Approach

For EC2 launch type:

# Create additional ENIs
aws ec2 create-network-interface --subnet-id subnet-123456 \
  --description "Container ENI 1" --groups sg-123456 \
  --private-ip-address 10.0.0.10

# Attach to instance
aws ec2 attach-network-interface --network-interface-id eni-123456 \
  --instance-id i-123456 --device-index 1

# Configure iptables rules
sudo iptables -t nat -A PREROUTING -d 203.0.113.5 -j DNAT --to-destination 10.0.0.10
sudo iptables -A FORWARD -d 10.0.0.10 -j ACCEPT

2. AWS VPC CNI Plugin for ECS

For Fargate or EC2:

{
  "taskDefinition": {
    "networkMode": "awsvpc",
    "requiresCompatibilities": ["FARGATE"],
    "ipcMode": "task",
    "proxyConfiguration": {
      "type": "APPMESH",
      "containerName": "envoy"
    }
  }
}

To ensure outbound requests use specific IPs:

# Create separate routing tables
ip route add default via 10.0.0.1 dev eth0 table 100
ip rule add from 10.0.0.10 lookup 100

# Or use source NAT
iptables -t nat -A POSTROUTING -o eth0 -s 10.0.0.10 -j SNAT --to-source 203.0.113.5
  • Use Terraform/CloudFormation to manage ENI lifecycle
  • Implement health checks for each IP-based service
  • Consider Network Load Balancer for TCP/UDP services
  • Monitor ENI attachment limits (varies by instance type)

When working with Amazon ECS, many developers encounter a limitation when trying to bind containers to specific host IP addresses. While Docker's native -p IP:hostPort:containerPort syntax provides this capability, ECS currently lacks direct support for this feature in task definitions.

ECS exposes network binding information through its API responses, as seen in this example:

{
  "bindIP": "0.0.0.0",
  "containerPort": 8021,
  "hostPort": 8021
}

However, this is read-only information - there's no way to configure the bindIP parameter when creating tasks.

Here are three approaches to achieve IP-based container routing in ECS:

1. Using EC2 Network Interfaces

# Create additional ENIs
aws ec2 create-network-interface \
  --subnet-id subnet-123456 \
  --description "Container-specific ENI" \
  --groups sg-123456 \
  --private-ip-address 10.0.0.10

Attach these to your EC2 instance and configure iptables rules to route traffic:

sudo iptables -t nat -A PREROUTING -d 10.0.0.10 -j DNAT --to-destination container-ip

2. Custom CNI Plugin Approach

For advanced use cases, consider implementing a custom CNI plugin:

{
  "type": "my-cni-plugin",
  "ipam": {
    "type": "host-local",
    "subnet": "10.0.0.0/24",
    "rangeStart": "10.0.0.100",
    "rangeEnd": "10.0.0.200"
  }
}

3. Proxy-Based Solution with ALB/NLB

While not exactly the same as IP binding, you can use:

aws elbv2 create-target-group \
  --name container1-tg \
  --protocol HTTP \
  --port 80 \
  --vpc-id vpc-123456 \
  --target-type ip \
  --health-check-path /health

For containers needing their own external IPs for outbound traffic:

# Create NAT gateway for each IP
aws ec2 create-nat-gateway \
  --subnet-id subnet-123456 \
  --allocation-id eipalloc-123456

Then configure route tables to route container traffic through the appropriate NAT gateway.

When dealing with SSL certificates and distinct services:

# Example task definition snippet
{
  "containerDefinitions": [
    {
      "portMappings": [
        {
          "hostPort": 443,
          "containerPort": 443,
          "protocol": "tcp"
        }
      ]
    }
  ]
}

Combine this with host-based routing at the load balancer level for proper SSL termination.