When running web applications on AWS Fargate, the fundamental issue with direct domain mapping stems from the ephemeral nature of Fargate tasks. Each deployment or scaling event can potentially change:
- The public IP address assigned to the ENI
- The underlying network interface configuration
- The security group associations
Here's a step-by-step approach to achieve stable domain mapping without ALB/NLB:
# Create Elastic Network Interface (ENI)
aws ec2 create-network-interface \
--subnet-id subnet-12345678 \
--groups sg-12345678 \
--description "Persistent ENI for Fargate tasks"
Key configuration parameters for your task definition:
{
"networkMode": "awsvpc",
"networkConfiguration": {
"awsvpcConfiguration": {
"subnets": ["subnet-12345678"],
"assignPublicIp": "ENABLED",
"securityGroups": ["sg-12345678"]
}
}
}
Create an A record that points to your ENI's private IP (more stable than public IP):
# Get ENI private IP
ENI_IP=$(aws ec2 describe-network-interfaces \
--network-interface-ids eni-12345678 \
--query 'NetworkInterfaces[0].PrivateIpAddress' \
--output text)
# Update Route 53 record
aws route53 change-resource-record-sets \
--hosted-zone-id Z1234567890 \
--change-batch '{
"Changes": [{
"Action": "UPSERT",
"ResourceRecordSet": {
"Name": "app.yourdomain.com",
"Type": "A",
"TTL": 60,
"ResourceRecords": [{"Value": "'$ENI_IP'"}]
}
}]
}'
Implement a Lambda function triggered by CloudWatch Events to automate ENI reassignment during deployments:
import boto3
def lambda_handler(event, context):
ec2 = boto3.client('ec2')
route53 = boto3.client('route53')
# Get new task's ENI
new_eni = ec2.describe_network_interfaces(
Filters=[{'Name': 'description', 'Values': ['*Fargate*']}]
)['NetworkInterfaces'][0]
# Update Route 53
route53.change_resource_record_sets(
HostedZoneId='Z1234567890',
ChangeBatch={
'Changes': [{
'Action': 'UPSERT',
'ResourceRecordSet': {
'Name': 'app.yourdomain.com',
'Type': 'A',
'TTL': 60,
'ResourceRecords': [{'Value': new_eni['PrivateIpAddress']}]
}
}]
}
)
- Configure security groups to allow HTTP/HTTPS traffic only from CloudFront if using CDN
- Implement WAF rules directly on the ENI level
- Rotate ENIs periodically for security best practices
Compared to ALB solution ($20+/month):
Component | Cost |
---|---|
ENI | $0.12/day ($3.6/month) |
Data Processing | $0.01/GB (vs ALB's $0.008/GB) |
Route 53 | $0.50/month per hosted zone |
When working with AWS Fargate tasks in public subnets, the ephemeral nature of public IPs creates DNS challenges. While Application/Network Load Balancers (ALB/NLB) provide stable endpoints, their cost may be prohibitive for lightweight applications. Here's a production-tested approach that survives task replacements.
Each Fargate task receives an Elastic Network Interface (ENI) with a persistent DNS name following this pattern:
ip-xxx-xxx-xxx-xxx.region.compute.internal
Though the internal DNS isn't directly accessible, we can extract the ENI's public DNS name through these steps:
1. Create a Route 53 Private Hosted Zone
aws route53 create-hosted-zone \
--name internal.yourdomain.com \
--vpc VPCRegion=us-east-1,VPCId=vpc-123456 \
--caller-reference $(date +%s) \
--hosted-zone-config Comment="ENI mapping"
2. Dynamically Update DNS Records
Use this Python Lambda function (triggered by ECS events) to maintain records:
import boto3
import os
def lambda_handler(event, context):
ecs = boto3.client('ecs')
route53 = boto3.client('route53')
cluster = event['detail']['clusterArn'].split('/')[-1]
task_arn = event['detail']['taskArn']
task = ecs.describe_tasks(cluster=cluster, tasks=[task_arn])
eni_id = task['tasks'][0]['attachments'][0]['details'][1]['value']
ec2 = boto3.client('ec2')
eni = ec2.describe_network_interfaces(NetworkInterfaceIds=[eni_id])
public_dns = eni['NetworkInterfaces'][0]['Association']['PublicDnsName']
route53.change_resource_record_sets(
HostedZoneId='YOUR_ZONE_ID',
ChangeBatch={
'Changes': [{
'Action': 'UPSERT',
'ResourceRecordSet': {
'Name': 'app.internal.yourdomain.com',
'Type': 'CNAME',
'TTL': 60,
'ResourceRecords': [{'Value': public_dns}]
}
}]
}
)
For infrastructure-as-code deployments:
Resources:
ENIMappingFunction:
Type: AWS::Lambda::Function
Properties:
Runtime: python3.8
Handler: index.lambda_handler
Role: !GetAtt LambdaExecutionRole.Arn
Code:
ZipFile: |
# Insert Lambda code from above
EventRule:
Type: AWS::Events::Rule
Properties:
EventPattern:
source: ["aws.ecs"]
detail-type: ["ECS Task State Change"]
Targets:
- Arn: !GetAtt ENIMappingFunction.Arn
- Set TTL values ≤ 60 seconds for faster failover
- Implement health checks on the CNAME record
- Monitor Route 53 update limits (default 1000/second)
- Consider weighted records for blue/green deployments
Compared to ALB ($16/month + LCU costs), this solution typically costs:
- $0.50/month per hosted zone
- $0.40 per million DNS queries
- Minimal Lambda invocation costs