Terraform aws_cloudwatch_metric_alarm Deep Dive

CloudWatch alarms are the backbone of AWS monitoring. They watch your metrics, fire when things go sideways, and trigger notifications or automated responses. Managing them through the AWS console works fine for a handful of alarms, but once you're dealing with dozens of services across multiple environments, Terraform becomes essential. This guide covers everything from basic metric alarms to composite alarms, SNS integrations, and patterns you'll actually use in production.

What CloudWatch Metric Alarms Do

A basic CloudWatch metric alarm watches a single metric over a time period you define. When the metric crosses a threshold for a specified number of evaluation periods, the alarm transitions from OK to ALARM state. You can attach actions to state transitions - most commonly sending an SNS notification that hits a Slack channel or PagerDuty.

The flow is:

  • CloudWatch Metric
  • Alarm Evaluates
  • Threshold Breached?
  • Yes → ALARM State → SNS Topic → Email/Slack/PagerDuty
  • No → OK State

Focus on metrics that indicate user-facing impact. CPU at 80% might not matter if response times are fine. Use appropriate evaluation periods. Single-period alarms are noisy. Most alarms should require 2-3 consecutive breaches before firing. Set treatmissingdata intentionally. The default is missing, which can cause confusing behavior. Use notBreaching for error count metrics and breaching for health check metrics. Tag your alarms consistently. When you've got 200 alarms, you'll thank yourself for adding environment, team, and service tags.

Terraform Resource Foundation

The aws_cloudwatch_metric_alarm resource provides a CloudWatch Metric Alarm resource.

Example Usage

hcl resource "aws_cloudwatch_metric_alarm" "foobar" { alarm_name = "terraform-test-foobar5" comparison_operator = "GreaterThanOrEqualToThreshold" evaluation_periods = "2" metric_name = "CPUUtilization" namespace = "AWS/EC2" period = "120" statistic = "Average" threshold = "80" alarm_description = "This metric monitor ec2 cpu utilization" insufficient_data_actions = [] }

Example in Conjunction with Scaling Policies

```hcl
resource "awsautoscalingpolicy" "bat" {
name = "foobar3-terraform-test"
scalingadjustment = 4
adjustment
type = "ChangeInCapacity"
cooldown = 300
autoscalinggroupname = "${awsautoscalinggroup.bar.name}"
}

resource "awscloudwatchmetricalarm" "bat" {
alarm
name = "terraform-test-foobar5"
comparisonoperator = "GreaterThanOrEqualToThreshold"
evaluation
periods = "2"
metricname = "CPUUtilization"
namespace = "AWS/EC2"
period = "120"
statistic = "Average"
threshold = "80"
dimensions {
AutoScalingGroupName = "${aws
autoscalinggroup.bar.name}"
}
alarm
description = "This metric monitor ec2 cpu utilization"
alarmactions = ["${awsautoscaling_policy.bat.arn}"]
}
```

Argument Reference
See related part of AWS Docs for details about valid values. The following arguments are supported:

Argument Required Description
alarm_name Yes The descriptive name for the alarm
comparison_operator
evaluation_periods
metric_name
namespace
period
statistic
threshold
alarm_description
alarm_actions
insufficientdataactions

SNS Foundation and Notification Actions

Before creating alarms, you need an SNS topic for notifications.

A typical alarm with SNS action is configured as:

hcl module "metric_alarm" { source = "terraform-aws-modules/cloudwatch/aws//modules/metric-alarm" version = "~> 3.0" alarm_name = "my-application-logs-errors" alarm_description = "Bad errors in my-application-logs" comparison_operator = "GreaterThanOrEqualToThreshold" evaluation_periods = 1 threshold = 10 period = 60 unit = "Count" namespace = "MyApplication" metric_name = "ErrorCount" statistic = "Maximum" alarm_actions = ["arn:aws:sns:eu-west-1:835367859852:my-sns-queue"] }

This pattern creates identical CPU alarms for every instance in a list using Terraform's for_each:

```hcl
variable "monitored_instances" {
type = map(string)
default = {
"web-1" = "i-0abc123def456789a"
"web-2" = "i-0abc123def456789b"
"worker-1" = "i-0abc123def456789c"
}
}

resource "awscloudwatchmetricalarm" "instancecpu" {
foreach = var.monitoredinstances
alarmname = "high-cpu-${each.key}"
comparison
operator = "GreaterThanThreshold"
evaluationperiods = 2
metric
name = "CPUUtilization"
namespace = "AWS/EC2"
period = 300
statistic = "Average"
threshold = 80
alarmdescription = "High CPU on ${each.key}"
dimensions = {
InstanceId = each.value
}
alarm
actions = [awssnstopic.warning_alerts.arn]
}
```

Reusable Modules for CloudWatch

Terraform module which creates Cloudwatch resources on AWS.

Module examples:

hcl module "log_metric_filter" { source = "terraform-aws-modules/cloudwatch/aws//modules/log-metric-filter" version = "~> 3.0" log_group_name = "my-application-logs" name = "error-metric" pattern = "ERROR" metric_transformation_namespace = "MyApplication" metric_transformation_name = "ErrorCount" }

Read Filter and Pattern Syntax for explanation of pattern.

hcl module "log_group" { source = "terraform-aws-modules/cloudwatch/aws//modules/log-group" version = "~> 3.0" name = "my-app" retention_in_days = 120 }

hcl module "log_stream" { source = "terraform-aws-modules/cloudwatch/aws//modules/log-stream" version = "~> 3.0" name = "stream1" log_group_name = "my-app" }

This submodule is useful when you need to create very similar alarms where only dimensions are different, eg, multiple AWS Lambda functions, but the rest of arguments are the same.

hcl module "metric_alarms" { source = "terraform-aws-modules/cloudwatch/aws//modules/metric-alarms-by-multiple-dimensions" version = "~> 3.0" alarm_name = "lambda-duration-" alarm_description = "Lambda duration is too high" comparison_operator = "GreaterThanOrEqualToThreshold" evaluation_periods = 1 threshold = 10 period = 60 unit = "Milliseconds" namespace = "AWS/Lambda" metric_name = "Duration" statistic = "Maximum" dimensions = { "lambda1" = { FunctionName = "index" }, "lambda2" = { FunctionName = "signup" }, } alarm_actions = ["arn:aws:sns:eu-west-1:835367859852:my-sns-queue"] }

Check out list of all AWS services that publish CloudWatch metrics for detailed information about each supported service.

hcl module "cis_alarms" { source = "terraform-aws-modules/cloudwatch/aws//modules/cis-alarms" version = "~> 3.0" log_group_name = "my-cloudtrail-logs" alarm_actions = ["arn:aws:sns:eu-west-1:835367859852:my-sns-queue"] }

AWS CloudTrail normally publishes logs into AWS CloudWatch Logs

Log Metric Filter and Alarm Pattern

Configuration in this directory creates Cloudwatch log metric based on pattern "ERROR" and connects it to Cloudwatch alarm which will push to SNS topic.

To run this example you need to execute:

bash $ terraform init $ terraform plan $ terraform apply

Note that this example may create resources which cost money. Run terraform destroy when you don't need these resources.

Name Version
terraform >= 1.0
aws >= 5.81

No providers.

Name Source Version
alarm ../../modules/metric-alarm n/a
awssnstopic ../fixtures/awssnstopic n/a
log_group ../../modules/log-group n/a
logmetricfilter ../../modules/log-metric-filter n/a

No resources.

No inputs.

Name Description
cloudwatchloggroup_arn ARN of Cloudwatch log group
cloudwatchloggroup_name Name of Cloudwatch log group
cloudwatchlogmetricfilterid The name of the metric filter
cloudwatchmetricalarm_arn The ARN of the Cloudwatch metric alarm
cloudwatchmetricalarm_id The ID of the Cloudwatch metric alarm

Anomaly Detection Alarms

Standard threshold-based alarms work for metrics with predictable ranges like CPU percentage, but for metrics like request counts that vary by time of day, anomaly detection is more useful.

This alarm uses CloudWatch anomaly detection to flag unusual API request patterns:

hcl resource "aws_cloudwatch_metric_alarm" "api_anomaly" { alarm_name = "api-request-anomaly" comparison_operator = "GreaterThanUpperThreshold" evaluation_periods = 2 threshold_metric_id = "ad1" alarm_description = "API request count is anomalously high" metric_query { id = "ad1" expression = "ANOMALY_DETECTION_BAND(m1, 2)" label = "Request Count (Expected)" return_data = true } metric_query { id = "m1" metric { metric_name = "RequestCount" namespace =

Production Patterns

  • Use for_each to avoid repetition for similar alarms across resources.
  • Create identical CPU alarms for every instance in a list with for_each.
  • Use modules for log metric filter, log group, log stream, metric alarm, metric alarms by multiple dimensions, and cis alarms.
  • Attach alarm actions to SNS topics for notifications and to autoscaling policies for automated responses.
  • Set evaluation_periods to 2 or more to reduce noise.
  • Tag alarms consistently for environment, team, and service.

Conclusion

CloudWatch alarms in Terraform give you version-controlled, repeatable monitoring that scales with your infrastructure. Start with the critical metrics for each service type, use for_each to avoid repetition, and lean on composite alarms to reduce alert fatigue. The configurations in this guide should cover the most common scenarios, but don't hesitate to customize thresholds based on your application's actual behavior patterns. Setting up the foundation with an SNS topic for notifications, building metric alarms with appropriate evaluation periods and comparison operators, and reusing modules for log metric filters and multi-dimensional alarms provides a production-ready monitoring baseline that remains maintainable as services grow.

Sources

  1. https://oneuptime.com/blog/post/2026-02-12-create-cloudwatch-alarms-terraform/view
  2. https://github.com/terraform-aws-modules/terraform-aws-cloudwatch
  3. https://www.koding.com/docs/terraform/providers/aws/r/cloudwatchmetricalarm.html/
  4. https://github.com/terraform-aws-modules/terraform-aws-cloudwatch/blob/master/examples/complete-log-metric-filter-and-alarm/README.md

Related Posts