,

ECS LiveTaskCount Alarm: Build It Step by Step

Developer desk with terminal, orange alarm bell and miniature containers, representing an ECS LiveTaskCount alarm

AWS just gave ECS a free service metric that finally counts every task doing work, and the quickest way to understand it is to build something with it. This walkthrough sets up an ECS LiveTaskCount alarm on a small Fargate service, puts the metric on a CloudWatch dashboard next to RunningTaskCount, proves the difference by changing the service, and tears everything down. It is all AWS CLI, so you can paste it into a terminal and follow along. Budget about 45 minutes and a few cents of Fargate time.

Some context before we start. LiveTaskCount, announced on October 13, 2026, counts tasks in the ACTIVATING, RUNNING, and DEACTIVATING states. It sits in the AWS/ECS namespace and does not require Container Insights, per the AWS announcement. RunningTaskCount, from Container Insights, counts only tasks that are fully running, and as of October 1 it is no longer charged. We will use both.

Prerequisites

You need a few things in place before the first command.

  • An AWS account and a user or role that can create ECS clusters, services, and task definitions, CloudWatch alarms and dashboards, SNS topics, and IAM service-linked roles.
  • AWS CLI v2 installed and configured, with a default Region. We use us-east-1 in the examples. Change it if you like, but keep it consistent.
  • A default VPC with at least two subnets in the Region, or the subnet IDs of a VPC you prefer to use. The tasks get public IPs so they can pull the image.
  • An email address you can check, for the alarm notification.
  • A shell with the jq tool installed. It keeps the commands short.

None of this needs a load balancer. That keeps the walkthrough cheap and fast, and it also means the draining behavior will be brief. We will talk about that honestly when we get there.

How the two metrics differ before you build anything

It helps to have the mental model straight before you start typing, because the whole exercise exists to make one distinction visible. ECS tasks move through lifecycle states, and each metric slices that lifecycle differently.

LiveTaskCount answers a capacity question: how many tasks exist right now that are starting up, serving, or on their way out? It includes ACTIVATING tasks, which are still coming online, RUNNING tasks, and DEACTIVATING tasks, which have been told to stop but may still be draining connections. It lives in the plain AWS/ECS namespace with ClusterName and ServiceName dimensions, and it supports Average, Minimum, and Maximum statistics, per the ECS metrics reference.

RunningTaskCount answers a health question: is the service at the shape I asked for? It counts tasks in RUNNING state, lives in the ECS/ContainerInsights namespace, and needs Container Insights set up on the cluster before any data appears. The AWS announcement describes it as a health and stability signal.

Why does this matter for an alarm? Suppose you write a floor alarm on RunningTaskCount. During a deployment, new tasks start and old tasks drain, and the running count can wobble in ways that have nothing to do with an outage. Suppose you write a per-task load calculation on RunningTaskCount. During a scale-in, you divide traffic by fewer tasks than are actually absorbing it. Both errors are small and both are annoying at three in the morning. Using the live count for capacity-style questions removes the arithmetic noise, and keeping the running count for health keeps its meaning intact.

The practical upshot is a division of labor. Alarms about whether you have enough working tasks should look at the live count with the Minimum statistic. Dashboards about service shape should show both lines together, because the gap between them is information. A gap that opens during a deployment and closes is normal. A gap that stays open means something is stuck, either tasks that cannot finish starting or tasks that cannot finish draining.

Build the ECS LiveTaskCount alarm and dashboard

The plan is simple. Create a Container Insights cluster, run one small service, query both metrics, wire up an alarm that fires when the service has fewer live tasks than it should, add a dashboard, and then force a change to watch the numbers move. Each step below does one job so you can stop and inspect after any of them.

Step 1: Set your variables

Start by putting the names you will reuse into environment variables. This avoids typos in the later commands and makes cleanup trivial.

Pick a cluster name, a service name, and your Region. We also look up the default VPC and grab two of its subnets. If your account has no default VPC, set SUBNETS by hand to two subnet IDs separated by a comma.

export AWS_REGION=us-east-1
export CLUSTER=livecount-demo
export SERVICE=web
export FAMILY=livecount-web

VPC_ID=$(aws ec2 describe-vpcs --filters Name=is-default,Values=true \
  --query 'Vpcs[0].VpcId' --output text)
export SUBNETS=$(aws ec2 describe-subnets --filters Name=vpc-id,Values=$VPC_ID \
  --query 'Subnets[0:2].SubnetId' --output text | tr '\t' ',')
export SG_ID=$(aws ec2 describe-security-groups \
  --filters Name=vpc-id,Values=$VPC_ID Name=group-name,Values=default \
  --query 'SecurityGroups[0].GroupId' --output text)

echo "$VPC_ID $SUBNETS $SG_ID"

If the echo prints three values and none says None, you are good. The default security group allows no inbound traffic from the internet, which is exactly what we want for a demo that only needs to exist, not serve visitors.

Step 2: Create the cluster with Container Insights

Next, create the ECS cluster. We turn on Container Insights so that RunningTaskCount shows up in the ECS/ContainerInsights namespace and we have something to compare against.

Container Insights metrics are billed as custom metrics, as the ECS Container Insights documentation explains, and log events are billed separately, so we will delete the cluster at the end. The setting accepts enabled, disabled, or enhanced. We use the standard enabled value, which is plenty for this exercise.

aws ecs create-cluster \
  --cluster-name $CLUSTER \
  --settings name=containerInsights,value=enabled

The response should show the cluster with status ACTIVE and the containerInsights setting set to enabled. If your account already has an account-level default for Container Insights, the cluster will pick up that default unless you set it explicitly like this.

Step 3: Register a minimal task definition

Now we need something to run. A Fargate task with a tiny web server image is perfect. We use an image from the public ECR gallery to avoid Docker Hub rate limits, and we keep the CPU and memory at the smallest Fargate size to keep the cost negligible.

Write the task definition to a file first so you can read it before you register it.

{
  "family": "livecount-web",
  "networkMode": "awsvpc",
  "requiresCompatibilities": ["FARGATE"],
  "cpu": "256",
  "memory": "512",
  "containerDefinitions": [
    {
      "name": "web",
      "image": "public.ecr.aws/nginx/nginx:stable",
      "essential": true,
      "portMappings": [{ "containerPort": 80, "protocol": "tcp" }]
    }
  ]
}

Save that as taskdef.json, then register it. There is no execution role because the image is public and we are not shipping logs to CloudWatch Logs from the container. That also keeps one more billable thing out of the picture.

aws ecs register-task-definition --cli-input-json file://taskdef.json \
  --query 'taskDefinition.taskDefinitionArn' --output text

You should get back an ARN that ends in livecount-web:1. If you see a validation error about CPU and memory, check that both are strings in the JSON, as shown.

Step 4: Create the service with three tasks

Create the service with a desired count of three. Three gives us room to scale down and see a visible change in the graph, and it is small enough to cost almost nothing. We assign public IPs so the tasks can pull the image from the public registry.

aws ecs create-service \
  --cluster $CLUSTER \
  --service-name $SERVICE \
  --task-definition $FAMILY \
  --desired-count 3 \
  --launch-type FARGATE \
  --network-configuration "awsvpcConfiguration={subnets=[$SUBNETS],securityGroups=[$SG_ID],assignPublicIp=ENABLED}"

Wait for the service to settle. This command blocks until the service reaches a steady state, which usually takes a couple of minutes while Fargate provisions the tasks.

aws ecs wait services-stable --cluster $CLUSTER --services $SERVICE
aws ecs describe-services --cluster $CLUSTER --services $SERVICE \
  --query 'services[0].{desired:desiredCount,running:runningCount,pending:pendingCount}'

When the output shows desired 3, running 3, and pending 0, the service is healthy. If the tasks keep failing, the usual culprit is a subnet without a route to the internet. Pick a different subnet or confirm the default VPC has an internet gateway attached.

Step 5: Query LiveTaskCount and RunningTaskCount

Metrics take a few minutes to show up after the first tasks start. ECS sends service metrics every minute, and only for services that have tasks in the RUNNING state, per the ECS metrics reference. Give it about five minutes, grab a coffee, and then query.

First, LiveTaskCount from the AWS/ECS namespace. The dimensions are ClusterName and ServiceName.

START=$(date -u -d '15 minutes ago' +%Y-%m-%dT%H:%M:%SZ)
END=$(date -u +%Y-%m-%dT%H:%M:%SZ)

aws cloudwatch get-metric-statistics \
  --namespace AWS/ECS --metric-name LiveTaskCount \
  --dimensions Name=ClusterName,Value=$CLUSTER Name=ServiceName,Value=$SERVICE \
  --start-time $START --end-time $END --period 60 \
  --statistics Average Maximum \
  --query 'sort_by(Datapoints,&Timestamp)[].[Timestamp,Average,Maximum]' --output table

On a Mac the date flag differs; use date -u -v-15M +%Y-%m-%dT%H:%M:%SZ instead. Now the same idea for RunningTaskCount, which lives in the ECS/ContainerInsights namespace and needs Container Insights to be fully set up before it shows data.

aws cloudwatch get-metric-statistics \
  --namespace ECS/ContainerInsights --metric-name RunningTaskCount \
  --dimensions Name=ClusterName,Value=$CLUSTER Name=ServiceName,Value=$SERVICE \
  --start-time $START --end-time $END --period 60 \
  --statistics Average \
  --query 'sort_by(Datapoints,&Timestamp)[].[Timestamp,Average]' --output table

Both tables should settle on 3.0. If LiveTaskCount has data but RunningTaskCount is empty, Container Insights may need a few more minutes. If both are empty after ten minutes, check that the tasks are actually RUNNING and that you used the right Region.

Step 6: Create the SNS topic for notifications

An alarm that nobody hears is decoration. Create an SNS topic and subscribe your email so the alarm has somewhere to go. Replace the address with yours.

TOPIC_ARN=$(aws sns create-topic --name livecount-demo-alerts \
  --query TopicArn --output text)
echo $TOPIC_ARN

aws sns subscribe \
  --topic-arn $TOPIC_ARN \
  --protocol email \
  --notification-endpoint you@example.com

AWS sends a confirmation email. Click the confirmation link or the subscription stays in a pending state and you will never receive a notification. This trips up more first-time alarm builders than any piece of CloudWatch syntax.

Step 7: Create the ECS LiveTaskCount alarm

Now the main event. We want an alarm that fires if the service ever has fewer than three live tasks. Using the Minimum statistic is the key choice. Average could hide a dip inside a minute, while Minimum catches it.

We also set treat-missing-data to breaching. ECS only publishes metrics for services with tasks in the RUNNING state, so a service that drops to zero tasks goes silent rather than reporting zero. Treating silence as a breach means a total outage still fires the alarm instead of leaving it in an unknown state.

aws cloudwatch put-metric-alarm \
  --alarm-name livecount-demo-capacity-floor \
  --alarm-description "Fewer than 3 live tasks in the web service" \
  --namespace AWS/ECS \
  --metric-name LiveTaskCount \
  --dimensions Name=ClusterName,Value=$CLUSTER Name=ServiceName,Value=$SERVICE \
  --statistic Minimum \
  --period 60 \
  --evaluation-periods 2 \
  --datapoints-to-alarm 2 \
  --threshold 3 \
  --comparison-operator LessThanThreshold \
  --treat-missing-data breaching \
  --alarm-actions $TOPIC_ARN \
  --ok-actions $TOPIC_ARN

The alarm needs two consecutive one-minute datapoints below three before it fires, which filters out a single noisy minute during a normal deployment. Check the result.

aws cloudwatch describe-alarms --alarm-names livecount-demo-capacity-floor \
  --query 'MetricAlarms[0].[AlarmName,StateValue,StateReason]' --output text

After a couple of minutes the state should read OK. If it says INSUFFICIENT_DATA, wait another minute or two. With breaching configured it should flip to OK as soon as it sees three live tasks.

Step 8: Add a dashboard comparing both metrics

A single alarm tells you when something is wrong. A dashboard tells you what normal looks like. Build one with two widgets: a line graph with both task counts overlaid, and the alarm status.

Write the dashboard body to a file. The region value must match your Region, and the cluster and service names must match what you created. Replace the placeholders with your values before you save it.

{
  "widgets": [
    {
      "type": "metric",
      "x": 0, "y": 0, "width": 18, "height": 6,
      "properties": {
        "title": "LiveTaskCount vs RunningTaskCount",
        "region": "us-east-1",
        "view": "timeSeries",
        "stat": "Average",
        "period": 60,
        "metrics": [
          ["AWS/ECS", "LiveTaskCount", "ClusterName", "livecount-demo", "ServiceName", "web", { "label": "Live" }],
          ["ECS/ContainerInsights", "RunningTaskCount", "ClusterName", "livecount-demo", "ServiceName", "web", { "label": "Running" }]
        ]
      }
    },
    {
      "type": "alarm",
      "x": 18, "y": 0, "width": 6, "height": 6,
      "properties": {
        "title": "Capacity floor",
        "alarms": ["arn:aws:cloudwatch:us-east-1:111122223333:alarm:livecount-demo-capacity-floor"]
      }
    }
  ]
}

Replace the account ID 111122223333 with your own. You can get it from the identity call below, which also publishes the dashboard.

aws sts get-caller-identity --query Account --output text
aws cloudwatch put-dashboard \
  --dashboard-name livecount-demo \
  --dashboard-body file://dashboard.json

Open CloudWatch in the console, choose Dashboards, and open livecount-demo. Both lines should sit at three. Right now they are identical, which is the boring baseline. The next step makes them interesting.

Wall dashboard with line charts and a dial gauge, representing a CloudWatch dashboard of ECS task counts

Step 9: Change the service and watch the lines

Time to prove the difference. We will do two things: scale down to see the alarm fire, then force a new deployment to see activation overlap.

First, scale the service from three to one. This drops below the floor of three, so the alarm should move to ALARM after two evaluation periods and send you an email.

aws ecs update-service --cluster $CLUSTER --service $SERVICE --desired-count 1

Watch the dashboard over the next few minutes. Be honest about what you will see. Because there is no load balancer in this setup, there is no connection draining to hold tasks in the DEACTIVATING state for long, so the two lines will fall together or within a minute of each other. The gap between LiveTaskCount and RunningTaskCount becomes dramatic when tasks sit behind a load balancer with a meaningful deregistration delay, which is the situation the new metric is designed for. If you want to see it in your own environment, run the same query pair on a production service around a scale-in.

Now scale back up and force a fresh deployment, which makes new tasks pass through ACTIVATING while old ones stop.

aws ecs update-service --cluster $CLUSTER --service $SERVICE \
  --desired-count 3 --force-new-deployment

During the rollout, LiveTaskCount can run ahead of RunningTaskCount while new tasks are starting. Check the alarm history to confirm the state changes you caused.

aws cloudwatch describe-alarm-history \
  --alarm-name livecount-demo-capacity-floor \
  --history-item-type StateUpdate \
  --max-records 5 \
  --query 'AlarmHistoryItems[].[Timestamp,HistorySummary]' --output table

You should see the alarm go to ALARM during the scale-down and return to OK once three live tasks are back. If the alarm never fired, confirm your SNS subscription is confirmed and that the alarm period and evaluation count had enough time to complete.

Step 10: Optional per-task math with metric math

One more trick worth keeping. If your service sits behind an ALB, divide request count by live tasks to get load per task that does not lie during a scale-in. This needs a load balancer, so treat it as a reference rather than part of the demo.

Metric math alarms use a metrics array. The expression divides the ALB request sum by the live task average.

[
  {"Id":"req","ReturnData":false,"MetricStat":{"Metric":{"Namespace":"AWS/ApplicationELB","MetricName":"RequestCount","Dimensions":[{"Name":"LoadBalancer","Value":"app/my-alb/0123456789abcdef"}]},"Period":60,"Stat":"Sum"}},
  {"Id":"live","ReturnData":false,"MetricStat":{"Metric":{"Namespace":"AWS/ECS","MetricName":"LiveTaskCount","Dimensions":[{"Name":"ClusterName","Value":"my-cluster"},{"Name":"ServiceName","Value":"my-service"}]},"Period":60,"Stat":"Average"}},
  {"Id":"perTask","Expression":"req / live","Label":"Requests per live task","ReturnData":true}
]

Create it with the same put-metric-alarm command, replacing the single-metric flags with a metrics argument pointing at that file.

aws cloudwatch put-metric-alarm \
  --alarm-name my-service-requests-per-live-task \
  --metrics file://perTask.json \
  --comparison-operator GreaterThanThreshold \
  --threshold 3000 \
  --evaluation-periods 3 \
  --treat-missing-data notBreaching

Set the threshold from your own traffic history. The number shown is a placeholder, not a recommendation. If you have no load balancer, skip this step entirely and move on to cleanup.

Troubleshooting the usual suspects

A few problems account for nearly every failed run of this exercise.

  • No data for LiveTaskCount. ECS publishes metrics only for services with tasks in the RUNNING state, at one-minute intervals. Wait five minutes, confirm the tasks are running, and check you are querying the same Region as the cluster.
  • No data for RunningTaskCount. This one lives in the ECS/ContainerInsights namespace and only appears once Container Insights is fully set up on the cluster. Confirm the cluster setting is enabled.
  • Tasks stuck in PROVISIONING or failing to start. The subnet needs a route to the internet to pull the public image. Pick a public subnet or add a gateway.
  • The alarm is stuck at INSUFFICIENT_DATA. With breaching configured it should resolve quickly. If not, make sure the dimension names and values exactly match the cluster and service, because a typo creates a valid alarm on a metric that does not exist.
  • No email. Check the spam folder and confirm the SNS subscription. A pending subscription delivers nothing.

What you built

You created a Fargate service with Container Insights enabled, queried LiveTaskCount from the AWS/ECS namespace and RunningTaskCount from ECS/ContainerInsights, and built a capacity-floor alarm using the Minimum statistic with missing data treated as a breach. You added a dashboard that overlays the two counts, forced a scale-down and a deployment to watch the numbers react, and saw how a requests-per-task alarm uses the live count as an honest denominator. The big idea is simple: use LiveTaskCount where you mean capacity, keep RunningTaskCount for health, and let the gap between them tell you when tasks are stuck starting or draining.

Take a moment to look at what the dashboard and alarm history are telling you before you tear things down. The point of the exercise is not the three tasks. It is the habit of putting two related numbers on one graph and asking why they differ. Try one more experiment if you have time: change the desired count to two, wait for the service to settle, and compare how quickly each line reacts. Then try stopping a single task by hand with the stop-task command and watch the service replace it. Each of these changes the shape of the lines slightly, and after three or four of them you will read the graphs the way you read a speedometer.

You also now have a reusable template. Swap the cluster and service names, adjust the threshold to match the number of tasks your real service needs at minimum, and the same alarm pattern protects a production service. Pair it with a deployment pipeline that waits for steady state, and you have a safety net that catches both bad deployments and silent task loss.

Cleanup

Remove everything so nothing keeps billing. Order matters: scale the service to zero, delete it, then delete the cluster, then the monitoring pieces.

aws ecs update-service --cluster $CLUSTER --service $SERVICE --desired-count 0
aws ecs delete-service --cluster $CLUSTER --service $SERVICE --force
aws ecs delete-cluster --cluster $CLUSTER

aws cloudwatch delete-alarms --alarm-names livecount-demo-capacity-floor
aws cloudwatch delete-dashboards --dashboard-names livecount-demo
aws sns delete-topic --topic-arn $TOPIC_ARN
aws ecs deregister-task-definition --task-definition livecount-web:1

If the cluster delete complains about active tasks, wait a minute for the service to finish draining and run it again. Container Insights metrics already published will age out on their own. For further reading on faster scaling signals, see the high-resolution auto scaling guide, and for what the metric costs you in context, see the CloudWatch pricing page.

ENKOMPASS IS HERE TO HELP

At Enkompass (enkompass.net), this is our home turf. We build and operate Fargate platforms with alarms, dashboards, and cost guardrails baked in from day one, so you do not have to reverse-engineer them from a runbook. If you’d rather have seasoned cloud people handle it instead of learning the hard way at 2 AM, let’s talk. Reach out through our contact page or call us directly at +1-828-436-5667 — we’ll help you turn this from a someday-project into a done-deal.

Leave a Reply

Ready when you are

Let’s map your route to AWS

A free 60-minute architecture review with a senior engineer. No slides, no SDR, just a plan.