Deploying IoT Backends on AWS: ECS Fargate vs Lambda vs EC2 Decision Guide
The three AWS compute options — ECS Fargate, Lambda, and EC2 — are not interchangeable. Each one has a workload profile it is designed for, and IoT backends are diverse enough that a single product might use all three simultaneously. Getting this decision wrong is expensive: Lambda's cold starts will kill your real-time WebSocket latency; EC2 will charge you for idle CPU at 3 AM when your devices are sleeping; Fargate will frustrate you when you actually need a long-running stateful process.
This guide walks through the decision framework we use at Code Caracal for every IoT backend we deploy.
The Three Workload Archetypes
Before comparing services, define your workload:
Always-on, stateful connections: MQTT bridges, WebSocket servers, real-time notification dispatchers. These processes hold open connections; they cannot restart mid-stream.
Event-driven, short-duration processing: Parsing a telemetry message, writing to DynamoDB, sending an alert email, running a data validation step. These fire, execute, and finish.
Heavy or long-running compute: ML model inference, video transcoding from a camera feed, large ETL jobs on historical sensor data. These need consistent CPU/memory for minutes to hours.
ECS Fargate: The Right Choice for Always-On IoT Services
If your IoT backend includes a WebSocket server, a persistent MQTT bridge, or any long-lived process that holds state in memory, ECS Fargate is the answer.
Lambda will not work here. AWS Lambda has a maximum execution duration of 15 minutes and cannot maintain a WebSocket connection across invocations. Attempting to use Lambda as a WebSocket server forces you to push all state to DynamoDB or ElastiCache on every message — adding 20–50 ms of latency per round trip and significant cost at scale.
Here is a production-ready ECS task definition for a Node.js MQTT bridge service:
{
"family": "iot-mqtt-bridge",
"networkMode": "awsvpc",
"requiresCompatibilities": ["FARGATE"],
"cpu": "512",
"memory": "1024",
"executionRoleArn": "arn:aws:iam::ACCOUNT_ID:role/ecsTaskExecutionRole",
"taskRoleArn": "arn:aws:iam::ACCOUNT_ID:role/iotMqttBridgeRole",
"containerDefinitions": [
{
"name": "mqtt-bridge",
"image": "ACCOUNT_ID.dkr.ecr.us-east-1.amazonaws.com/mqtt-bridge:latest",
"essential": true,
"portMappings": [
{ "containerPort": 3000, "protocol": "tcp" },
{ "containerPort": 1883, "protocol": "tcp" }
],
"environment": [
{ "name": "NODE_ENV", "value": "production" },
{ "name": "MQTT_BROKER_URL", "value": "mqtt://localhost:1883" }
],
"secrets": [
{
"name": "DB_PASSWORD",
"valueFrom": "arn:aws:secretsmanager:us-east-1:ACCOUNT_ID:secret:iot/db-password"
}
],
"healthCheck": {
"command": ["CMD-SHELL", "curl -f http://localhost:3000/health || exit 1"],
"interval": 30,
"timeout": 5,
"retries": 3,
"startPeriod": 10
},
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/mqtt-bridge",
"awslogs-region": "us-east-1",
"awslogs-stream-prefix": "ecs"
}
}
}
]
}
Auto-scaling on Fargate: Attach an Application Auto Scaling policy to your ECS service. For WebSocket servers, scale on the custom metric ActiveConnections rather than CPU — a well-written WebSocket server is async and will show low CPU even with thousands of connections.
Lambda: The Right Choice for Event-Driven IoT Processing
Lambda excels at the processing layer behind AWS IoT Core. The Rules Engine publishes a message to an SQS queue or invokes Lambda directly; Lambda transforms the payload, writes to DynamoDB or InfluxDB, and terminates. Total execution time: 50–300 ms.
// Lambda handler: process telemetry from IoT Rules Engine
exports.handler = async (event) => {
const { deviceId, temperature, humidity, timestamp } = event; // Validate range
if (temperature < -40 || temperature > 125) {
console.warn(Out-of-range reading from ${deviceId}: ${temperature}°C);
return { statusCode: 400, body: 'Invalid reading' };
}
// Write to DynamoDB
await dynamodb.put({
TableName: process.env.TABLE_NAME,
Item: {
pk: DEVICE#${deviceId},
sk: TS#${timestamp},
temperature,
humidity,
ttl: Math.floor(Date.now() / 1000) + 7776000, // 90-day TTL
},
}).promise();
// Trigger alert if threshold breached
if (temperature > 80) {
await sns.publish({
TopicArn: process.env.ALERT_TOPIC_ARN,
Message: JSON.stringify({ deviceId, temperature, timestamp }),
Subject: 'High temperature alert',
}).promise();
}
return { statusCode: 200 };
};
The cold start problem: Lambda cold starts for a Node.js function average 200–400 ms. For IoT telemetry processing this is acceptable — devices do not notice processing latency. Where cold starts are unacceptable is API endpoints that your Flutter app or React dashboard calls directly. Provision concurrency for those functions, or route them through ECS instead.
At 1 million Lambda invocations per month (typical for a 500-device fleet sending data every 30 seconds), cost is approximately $0.20/month — essentially free.
EC2: When You Actually Need It
EC2 earns its place for heavy, long-running compute that exceeds Lambda's 15-minute limit or requires persistent local storage. Common IoT use cases:
For EC2, use Reserved Instances for baseline capacity and Spot Instances for batch workloads. A t3.medium Reserved Instance (1-year, no upfront) runs approximately $22/month versus $35/month on-demand.
Cost Comparison at Scale
| Scale | Lambda | ECS Fargate (0.5 vCPU / 1 GB) | EC2 t3.medium | |---|---|---|---| | 100K msgs/day | ~$0.02/month | $25/month | $35/month | | 1M msgs/day | ~$0.20/month | $25/month | $35/month | | 10M msgs/day | ~$2/month | $50/month (scaled) | $70/month | | 100M msgs/day | ~$20/month | $200/month (scaled) | $140/month |
At very high message volumes, Lambda remains cheapest for pure processing. Fargate becomes expensive only if you over-provision task count. EC2 wins for sustained heavy compute but requires operational overhead.
Our Decision Rule
Apply this in order:
Most IoT backends use all three: Fargate for the real-time layer, Lambda for the processing layer, EC2 (or RDS) for the database layer.
---
Not sure which compute mix fits your IoT product? [Contact Code Caracal](/contact) and we will design your AWS architecture in a free scoping session.