Getting Started with Fargate Monitoring
Amazon ECS Fargate is a serverless compute engine for containers that removes the need to provision, manage, and scale underlying infrastructure. While Fargate abstracts away the host layer, it does not remove the need for observability. In fact, because you lose direct access to the host OS and daemons, you must rely entirely on AWS-native tooling to understand how your containers are behaving. This tutorial covers everything you need to know about monitoring Fargate workloads: the built-in metrics, how to create alarms, how to build dashboards, and how to instrument your application code with custom metrics.
What Monitoring Means for Fargate
Monitoring a Fargate deployment involves collecting and acting on three layers of data:
- Service-level metrics – CPU utilization, memory utilization, network traffic, and task counts. These come from the ECS and Fargate control planes automatically.
- Container-level metrics – Per-container breakdowns of CPU, memory, disk, and network, available when you enable Container Insights.
- Application-level metrics – Custom business metrics your code emits (request latency, error rates, queue depth, etc.) using CloudWatch Embedded Metric Format or the PutMetricData API.
All these feed into Amazon CloudWatch, where you can visualize them on dashboards and trigger alarms that notify you or auto-scale your service.
Why Monitoring Matters for Fargate
Without monitoring, you are flying blind. Fargate bills you per vCPU-second and per GB-second of memory reservation. An over-provisioned task burns money silently. An under-provisioned task gets throttled or OOM-killed, causing outages. Monitoring helps you:
- Right-size tasks – Find the optimal CPU/memory combination by watching actual usage over days or weeks.
- Detect anomalies – Memory leaks, traffic spikes, or failing deployments show up in metric trends before users complain.
- Trigger automated responses – Scale out via Service Auto Scaling, roll back deployments, or page on-call engineers.
- Debug application issues – Correlate CPU spikes with application logs to pinpoint expensive code paths.
Default Metrics: What You Get for Free
Every Fargate service automatically sends the following metrics to CloudWatch (in the AWS/ECS namespace) at one-minute granularity, at no extra cost:
CPUUtilization– Percentage of the reserved vCPU units actually consumed.MemoryUtilization– Percentage of the reserved memory actually consumed.CPUReservation– Reserved vCPU units relative to the total registered for the cluster.MemoryReservation– Reserved memory relative to the total registered.GPUReservation– If you use GPU-backed tasks.TaskCount– Number of running tasks, per service.
These metrics are available under dimensions like ClusterName and ServiceName. The TaskCount metric is particularly important for tracking deployments and scaling behavior.
Viewing Default Metrics in the Console
Navigate to CloudWatch → Metrics → AWS/ECS. You can graph any of these by selecting the metric and choosing a statistic like Average, Sum, or Maximum over your preferred period.
Enabling Container Insights
Container Insights provides per-container granularity, including task-level breakdowns, disk I/O, and network statistics. It charges a small amount per metric observation. Enable it per-cluster.
Via the AWS Console
ECS → Clusters → select your cluster → "Update Cluster" → toggle "Container Insights" on.
Via AWS CLI
aws ecs update-cluster-settings \
--cluster my-fargate-cluster \
--settings name=containerInsights,value=enabled \
--region us-east-1
Via CloudFormation
Resources:
FargateCluster:
Type: AWS::ECS::Cluster
Properties:
ClusterName: my-fargate-cluster
ClusterSettings:
- Name: containerInsights
Value: enabled
Via CDK (TypeScript)
const cluster = new ecs.Cluster(this, 'FargateCluster', {
clusterName: 'my-fargate-cluster',
containerInsights: true,
});
Via Terraform
resource "aws_ecs_cluster" "fargate" {
name = "my-fargate-cluster"
setting {
name = "containerInsights"
value = "enabled"
}
}
Once enabled, Container Insights populates the ECS/ContainerInsights namespace with metrics like task_cpu_utilization, task_memory_utilization, container_cpu_utilization, container_memory_utilization, network_rx_bytes, network_tx_bytes, and more. These appear within 2-3 minutes.
Building CloudWatch Alarms
Alarms watch a single metric (or a math expression combining several) and change state from OK to ALARM when a threshold is breached. They can send notifications via SNS, trigger EC2 actions, or feed into auto-scaling policies.
High CPU Alarm (CLI Example)
aws cloudwatch put-metric-alarm \
--alarm-name "fargate-high-cpu" \
--alarm-description "Alert when service CPU exceeds 80% for 5 minutes" \
--namespace "AWS/ECS" \
--metric-name "CPUUtilization" \
--statistic "Average" \
--period 300 \
--evaluation-periods 1 \
--threshold 80 \
--comparison-operator GreaterThanThreshold \
--dimensions Name=ClusterName,Value=my-fargate-cluster Name=ServiceName,Value=my-service \
--alarm-actions arn:aws:sns:us-east-1:123456789012:ops-alerts
Memory Utilization Alarm with Missing Data Handling
aws cloudwatch put-metric-alarm \
--alarm-name "fargate-high-memory" \
--namespace "AWS/ECS" \
--metric-name "MemoryUtilization" \
--statistic "Maximum" \
--period 300 \
--evaluation-periods 2 \
--threshold 85 \
--comparison-operator GreaterThanThreshold \
--treat-missing-data "breaching" \
--dimensions Name=ClusterName,Value=my-fargate-cluster Name=ServiceName,Value=my-service \
--alarm-actions arn:aws:sns:us-east-1:123456789012:ops-alerts
The --treat-missing-data breaching flag is critical: if a Fargate task crashes and stops reporting metrics, the alarm will fire instead of silently staying green. Always think about missing data semantics when designing alarms for ephemeral container workloads.
Task Count Alarm (Detecting Crash Loops)
aws cloudwatch put-metric-alarm \
--alarm-name "fargate-task-count-low" \
--namespace "AWS/ECS" \
--metric-name "TaskCount" \
--statistic "Minimum" \
--period 300 \
--evaluation-periods 2 \
--threshold 1 \
--comparison-operator LessThanThreshold \
--treat-missing-data "breaching" \
--dimensions Name=ClusterName,Value=my-fargate-cluster Name=ServiceName,Value=my-service \
--alarm-actions arn:aws:sns:us-east-1:123456789012:ops-alerts
CDK Alarm Example (TypeScript)
import * as cw from 'aws-cdk-lib/aws-cloudwatch';
import * as sns from 'aws-cdk-lib/aws-sns';
const alarmTopic = new sns.Topic(this, 'OpsAlerts');
new cw.Alarm(this, 'FargateCpuAlarm', {
metric: new cw.Metric({
namespace: 'AWS/ECS',
metricName: 'CPUUtilization',
statistic: 'Average',
period: cdk.Duration.minutes(5),
dimensionsMap: {
ClusterName: cluster.clusterName,
ServiceName: service.serviceName,
},
}),
threshold: 80,
evaluationPeriods: 2,
comparisonOperator: cw.ComparisonOperator.GREATER_THAN_THRESHOLD,
treatMissingData: cw.TreatMissingData.BREACHING,
alarmDescription: 'Triggers when Fargate CPU exceeds 80% for 10 minutes',
});
alarm.addAlarmAction(new cwActions.SnsAction(alarmTopic));
Creating Dashboards
CloudWatch dashboards let you pin multiple metric widgets onto a single pane of glass. You can create them via the console, the CLI, or infrastructure-as-code. Dashboards are JSON structures that define widgets, their layout, and their data sources.
Dashboard via CLI with Multiple Widgets
aws cloudwatch put-dashboard \
--dashboard-name "Fargate-Overview" \
--dashboard-body '{
"widgets": [
{
"type": "metric",
"x": 0, "y": 0, "width": 12, "height": 6,
"properties": {
"metrics": [
["AWS/ECS", "CPUUtilization", "ClusterName", "my-fargate-cluster", "ServiceName", "my-service", {"stat": "Average", "period": 60}],
["AWS/ECS", "MemoryUtilization", "ClusterName", "my-fargate-cluster", "ServiceName", "my-service", {"stat": "Average", "period": 60}]
],
"view": "timeSeries",
"stacked": false,
"region": "us-east-1",
"title": "CPU & Memory Utilization"
}
},
{
"type": "metric",
"x": 12, "y": 0, "width": 12, "height": 6,
"properties": {
"metrics": [
["AWS/ECS", "TaskCount", "ClusterName", "my-fargate-cluster", "ServiceName", "my-service", {"stat": "Average", "period": 60}]
],
"view": "timeSeries",
"region": "us-east-1",
"title": "Running Task Count"
}
},
{
"type": "metric",
"x": 0, "y": 6, "width": 24, "height": 6,
"properties": {
"metrics": [
["ECS/ContainerInsights", "network_rx_bytes", "ClusterName", "my-fargate-cluster", "ServiceName", "my-service", {"stat": "Sum", "period": 60}],
["ECS/ContainerInsights", "network_tx_bytes", "ClusterName", "my-fargate-cluster", "ServiceName", "my-service", {"stat": "Sum", "period": 60}]
],
"view": "timeSeries",
"region": "us-east-1",
"title": "Network Throughput (Container Insights)"
}
}
]
}'
CDK Dashboard Example
import * as cw from 'aws-cdk-lib/aws-cloudwatch';
const dashboard = new cw.Dashboard(this, 'FargateDashboard', {
dashboardName: 'Fargate-Overview',
});
// CPU widget
dashboard.addWidgets(
new cw.GraphWidget({
title: 'CPU Utilization',
left: [
new cw.Metric({
namespace: 'AWS/ECS',
metricName: 'CPUUtilization',
statistic: 'Average',
period: cdk.Duration.minutes(1),
dimensionsMap: {
ClusterName: cluster.clusterName,
ServiceName: service.serviceName,
},
}),
],
width: 12,
height: 6,
})
);
// Memory widget
dashboard.addWidgets(
new cw.GraphWidget({
title: 'Memory Utilization',
left: [
new cw.Metric({
namespace: 'AWS/ECS',
metricName: 'MemoryUtilization',
statistic: 'Average',
period: cdk.Duration.minutes(1),
dimensionsMap: {
ClusterName: cluster.clusterName,
ServiceName: service.serviceName,
},
}),
],
width: 12,
height: 6,
})
);
// Task count widget
dashboard.addWidgets(
new cw.SingleValueWidget({
title: 'Running Tasks',
metrics: [
new cw.Metric({
namespace: 'AWS/ECS',
metricName: 'TaskCount',
statistic: 'Average',
period: cdk.Duration.minutes(1),
dimensionsMap: {
ClusterName: cluster.clusterName,
ServiceName: service.serviceName,
},
}),
],
width: 6,
height: 4,
})
);
Container Insights Deep Dive
When Container Insights is enabled, ECS also streams performance log events to CloudWatch Logs under the log group /aws/ecs/containerinsights/. These logs contain structured JSON records that you can query with CloudWatch Logs Insights.
Sample Container Insights Performance Log
{
"type": "Container",
"container_name": "web",
"task_id": "abc123...",
"cluster_arn": "arn:aws:ecs:...",
"cpu_utilization": 12.5,
"memory_utilization": 34.2,
"memory_limit_mb": 512,
"network": {
"rx_bytes": 123456,
"tx_bytes": 78901
},
"storage": {
"read_bytes": 0,
"write_bytes": 4096
}
}
Querying with Logs Insights
fields @timestamp, container_name, cpu_utilization, memory_utilization, network.rx_bytes, network.tx_bytes
| filter type = "Container"
| sort @timestamp desc
| limit 50
This query returns the last 50 container-level observations. You can build dashboard widgets directly from Logs Insights queries, or use the structured metrics already aggregated by Container Insights in the ECS/ContainerInsights namespace.
Custom Metrics: The Embedded Metric Format
For application-level observability—request latency, error counts, order processing rates—you should emit custom metrics from your code. The most cost-effective and structured way is CloudWatch Embedded Metric Format (EMF). Your application writes JSON-formatted metric records to stdout (or a log stream), and CloudWatch automatically extracts and aggregates them as metric data points. You pay only for the log ingestion, not per-metric-observation API calls.
EMF with Python (inside a Fargate Task)
import json
import time
import random
from datetime import datetime
def emit_metric():
# Construct an EMF blob
emf_blob = {
"_aws": {
"Timestamp": int(time.time() * 1000),
"CloudWatchMetrics": [
{
"Namespace": "MyApplication",
"Dimensions": [["service", "environment"]],
"Metrics": [
{"Name": "RequestLatency", "Unit": "Milliseconds"},
{"Name": "RequestCount", "Unit": "Count"}
]
}
]
},
"service": "order-api",
"environment": "production",
"RequestLatency": random.randint(5, 200),
"RequestCount": 1,
"traceId": "trace-abc-123"
}
# Write as a single line to stdout
print(json.dumps(emf_blob))
# Emit every 10 seconds in a real application
while True:
emit_metric()
time.sleep(10)
When your Fargate task logs this to stdout, the CloudWatch agent (or the FireLens log router if configured) picks it up. CloudWatch automatically creates the metrics RequestLatency and RequestCount in the MyApplication namespace, dimensioned by service and environment. You can then alarm and dashboard these just like any other metric.
EMF with Node.js (Express Middleware)
const os = require('os');
// EMF helper function
function emitMetric(name, value, unit, dimensions) {
const blob = {
_aws: {
Timestamp: Date.now(),
CloudWatchMetrics: [
{
Namespace: 'MyNodeApp',
Dimensions: [Object.keys(dimensions)],
Metrics: [{ Name: name, Unit: unit }]
}
]
},
...dimensions,
[name]: value
};
process.stdout.write(JSON.stringify(blob) + '\n');
}
// Express middleware for request latency
function latencyMiddleware(req, res, next) {
const start = Date.now();
res.on('finish', () => {
const latencyMs = Date.now() - start;
emitMetric('HttpLatency', latencyMs, 'Milliseconds', {
route: req.path,
method: req.method,
host: os.hostname()
});
});
next();
}
// In your Express app:
// app.use(latencyMiddleware);
The beauty of EMF is that it requires no SDK calls—just structured logging. It's ideal for high-throughput services where calling PutMetricData for every event would be throttled or expensive.
Using the CloudWatch Agent Sidecar for EMF
If you want richer metric collection (e.g., memory pressure, disk usage inside the container), you can run the CloudWatch agent as a sidecar in your task definition. Here is a task definition excerpt:
{
"family": "my-service",
"containerDefinitions": [
{
"name": "app",
"image": "my-app-image:latest",
"essential": true,
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/my-service",
"awslogs-region": "us-east-1",
"awslogs-stream-prefix": "app"
}
}
},
{
"name": "cw-agent",
"image": "public.ecr.aws/cloudwatch-agent/cloudwatch-agent:latest",
"essential": false,
"secrets": [
{
"name": "CW_CONFIG",
"valueFrom": "arn:aws:ssm:us-east-1:123456789012:parameter/cw-agent-config"
}
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/my-service",
"awslogs-region": "us-east-1",
"awslogs-stream-prefix": "cw-agent"
}
}
}
]
}
The CloudWatch agent reads its config from SSM Parameter Store, collects OS-level and custom metrics, and forwards them to CloudWatch. This is the recommended approach for production workloads that need detailed system metrics beyond what Container Insights provides.
PutMetricData API for Low-Volume Custom Metrics
For low-frequency metrics (e.g., deployment events, batch job completions), you can use the PutMetricData API directly from your application code or from a Lambda function that reacts to ECS events.
Python boto3 Example
import boto3
from datetime import datetime
cloudwatch = boto3.client('cloudwatch')
response = cloudwatch.put_metric_data(
Namespace='MyApplication/Deployments',
MetricData=[
{
'MetricName': 'DeploymentSuccess',
'Timestamp': datetime.utcnow(),
'Value': 1.0,
'Unit': 'Count',
'Dimensions': [
{'Name': 'Service', 'Value': 'order-api'},
{'Name': 'Environment', 'Value': 'production'}
]
}
]
)
Keep in mind that PutMetricData has API limits (150 transactions per second per region per account) and a cost per 1,000 metrics delivered. For high-cardinality or high-frequency metrics, EMF is almost always the better choice.
Connecting Alarms to Auto-Scaling
Fargate Service Auto Scaling uses CloudWatch alarms under the hood. When you configure target tracking or step scaling policies, ECS creates and manages alarms for you. However, you can also build your own alarm-driven scaling using Application Auto Scaling with custom metrics.
Registering a Scalable Target (CLI)
aws application-autoscaling register-scalable-target \
--service-namespace ecs \
--resource-id service/my-fargate-cluster/my-service \
--scalable-dimension ecs:service:DesiredCount \
--min-capacity 2 \
--max-capacity 10
Attaching a Custom Metric Scaling Policy
aws application-autoscaling put-scaling-policy \
--service-namespace ecs \
--resource-id service/my-fargate-cluster/my-service \
--scalable-dimension ecs:service:DesiredCount \
--policy-name "QueueDepthScaling" \
--policy-type "StepScaling" \
--step-scaling-policy-configuration '{
"AdjustmentType": "PercentChangeInCapacity",
"StepAdjustments": [
{"MetricIntervalLowerBound": 0, "ScalingAdjustment": 10},
{"MetricIntervalLowerBound": 100, "ScalingAdjustment": 50}
],
"Cooldown": 300
}'
Then create a CloudWatch alarm on your custom QueueDepth metric and link it to this policy via --alarm-actions pointing to the scaling policy ARN. This allows you to scale Fargate services on business metrics, not just CPU or memory.
Best Practices
- Enable Container Insights everywhere – The per-task visibility is worth the small cost. It shows you which specific task in a service is misbehaving.
- Always set
treatMissingDatatobreachingon critical alarms. Fargate tasks can disappear abruptly; a missing metric is itself a signal. - Use composite alarms for complex conditions – Combine a CPU alarm with a task-count alarm so you only page when both CPU is high AND you have tasks running (not during a deployment when tasks are naturally cycling).
- Build dashboards per service and per environment – A single mega-dashboard becomes noisy. Create focused views: one for production order-api, one for staging, one for CI/CD pipeline metrics.
- Embed metrics via EMF, not PutMetricData – It scales better, costs less at high volume, and ties metrics to your log stream for easy correlation.
- Tag and dimension everything consistently – Use
ClusterName,ServiceName,Environment, andVersionas dimensions so you can slice metrics across any axis. - Monitor task placement and pull failures – ECS emits
ServiceThrottleEventsandTaskSetExternalEventsin ECS event stream. Pipe these to CloudWatch via EventBridge rules to detect deployment failures or capacity issues. - Set up anomaly detection on cost-related metrics – Use CloudWatch anomaly detection on
CPUUtilizationandMemoryUtilizationto catch regressions after deployments without hard thresholds. - Archive metrics for long-term analysis – CloudWatch retains metrics at 1-minute granularity for 15 days. For capacity planning over months, stream metrics to S3 via CloudWatch Metric Streams and query with Athena.
Conclusion
Monitoring Fargate is not an afterthought—it's a first-class engineering concern that directly impacts reliability, cost, and developer velocity. By layering the free ECS metrics, Container Insights for per-task granularity, and Embedded Metric Format for application-level signals, you build a complete observability picture. Pair these metrics with well-designed alarms (with proper missing-data handling) and focused dashboards, and you transform Fargate from a black-box container runtime into a transparent, manageable platform. The code patterns shown here—CloudFormation, CDK, Terraform, Python, and Node.js—should give you everything you need to instrument your own Fargate workloads from day one.