Configure AWS Application Auto Scaling Policies

The aws:appautoscaling/policy:Policy resource, part of the Pulumi AWS provider, defines Application Auto Scaling policies that adjust capacity for DynamoDB, ECS, Aurora, MSK, and other AWS services based on metrics or forecasts. This guide focuses on three capabilities: target tracking policies with predefined and custom metrics, step scaling for threshold-based adjustments, and predictive scaling for forecasted load patterns.

Policies require appautoscaling.Target resources that define the scalable dimension and capacity bounds. Step scaling policies also require CloudWatch alarms to trigger adjustments. The examples are intentionally small. Combine them with your own targets, alarms, and service infrastructure.

Scale DynamoDB read capacity with target tracking

DynamoDB tables often need to scale read capacity automatically as query patterns change. Target tracking policies adjust capacity to maintain a specific utilization percentage.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const dynamodbTableReadTarget = new aws.appautoscaling.Target("dynamodb_table_read_target", {
    maxCapacity: 100,
    minCapacity: 5,
    resourceId: "table/tableName",
    scalableDimension: "dynamodb:table:ReadCapacityUnits",
    serviceNamespace: "dynamodb",
});
const dynamodbTableReadPolicy = new aws.appautoscaling.Policy("dynamodb_table_read_policy", {
    name: pulumi.interpolate`DynamoDBReadCapacityUtilization:${dynamodbTableReadTarget.resourceId}`,
    policyType: "TargetTrackingScaling",
    resourceId: dynamodbTableReadTarget.resourceId,
    scalableDimension: dynamodbTableReadTarget.scalableDimension,
    serviceNamespace: dynamodbTableReadTarget.serviceNamespace,
    targetTrackingScalingPolicyConfiguration: {
        predefinedMetricSpecification: {
            predefinedMetricType: "DynamoDBReadCapacityUtilization",
        },
        targetValue: 70,
    },
});
import pulumi
import pulumi_aws as aws

dynamodb_table_read_target = aws.appautoscaling.Target("dynamodb_table_read_target",
    max_capacity=100,
    min_capacity=5,
    resource_id="table/tableName",
    scalable_dimension="dynamodb:table:ReadCapacityUnits",
    service_namespace="dynamodb")
dynamodb_table_read_policy = aws.appautoscaling.Policy("dynamodb_table_read_policy",
    name=dynamodb_table_read_target.resource_id.apply(lambda resource_id: f"DynamoDBReadCapacityUtilization:{resource_id}"),
    policy_type="TargetTrackingScaling",
    resource_id=dynamodb_table_read_target.resource_id,
    scalable_dimension=dynamodb_table_read_target.scalable_dimension,
    service_namespace=dynamodb_table_read_target.service_namespace,
    target_tracking_scaling_policy_configuration={
        "predefined_metric_specification": {
            "predefined_metric_type": "DynamoDBReadCapacityUtilization",
        },
        "target_value": 70,
    })
package main

import (
	"fmt"

	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/appautoscaling"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		dynamodbTableReadTarget, err := appautoscaling.NewTarget(ctx, "dynamodb_table_read_target", &appautoscaling.TargetArgs{
			MaxCapacity:       pulumi.Int(100),
			MinCapacity:       pulumi.Int(5),
			ResourceId:        pulumi.String("table/tableName"),
			ScalableDimension: pulumi.String("dynamodb:table:ReadCapacityUnits"),
			ServiceNamespace:  pulumi.String("dynamodb"),
		})
		if err != nil {
			return err
		}
		_, err = appautoscaling.NewPolicy(ctx, "dynamodb_table_read_policy", &appautoscaling.PolicyArgs{
			Name: dynamodbTableReadTarget.ResourceId.ApplyT(func(resourceId string) (string, error) {
				return fmt.Sprintf("DynamoDBReadCapacityUtilization:%v", resourceId), nil
			}).(pulumi.StringOutput),
			PolicyType:        pulumi.String("TargetTrackingScaling"),
			ResourceId:        dynamodbTableReadTarget.ResourceId,
			ScalableDimension: dynamodbTableReadTarget.ScalableDimension,
			ServiceNamespace:  dynamodbTableReadTarget.ServiceNamespace,
			TargetTrackingScalingPolicyConfiguration: &appautoscaling.PolicyTargetTrackingScalingPolicyConfigurationArgs{
				PredefinedMetricSpecification: &appautoscaling.PolicyTargetTrackingScalingPolicyConfigurationPredefinedMetricSpecificationArgs{
					PredefinedMetricType: pulumi.String("DynamoDBReadCapacityUtilization"),
				},
				TargetValue: pulumi.Float64(70),
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var dynamodbTableReadTarget = new Aws.AppAutoScaling.Target("dynamodb_table_read_target", new()
    {
        MaxCapacity = 100,
        MinCapacity = 5,
        ResourceId = "table/tableName",
        ScalableDimension = "dynamodb:table:ReadCapacityUnits",
        ServiceNamespace = "dynamodb",
    });

    var dynamodbTableReadPolicy = new Aws.AppAutoScaling.Policy("dynamodb_table_read_policy", new()
    {
        Name = dynamodbTableReadTarget.ResourceId.Apply(resourceId => $"DynamoDBReadCapacityUtilization:{resourceId}"),
        PolicyType = "TargetTrackingScaling",
        ResourceId = dynamodbTableReadTarget.ResourceId,
        ScalableDimension = dynamodbTableReadTarget.ScalableDimension,
        ServiceNamespace = dynamodbTableReadTarget.ServiceNamespace,
        TargetTrackingScalingPolicyConfiguration = new Aws.AppAutoScaling.Inputs.PolicyTargetTrackingScalingPolicyConfigurationArgs
        {
            PredefinedMetricSpecification = new Aws.AppAutoScaling.Inputs.PolicyTargetTrackingScalingPolicyConfigurationPredefinedMetricSpecificationArgs
            {
                PredefinedMetricType = "DynamoDBReadCapacityUtilization",
            },
            TargetValue = 70,
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.appautoscaling.Target;
import com.pulumi.aws.appautoscaling.TargetArgs;
import com.pulumi.aws.appautoscaling.Policy;
import com.pulumi.aws.appautoscaling.PolicyArgs;
import com.pulumi.aws.appautoscaling.inputs.PolicyTargetTrackingScalingPolicyConfigurationArgs;
import com.pulumi.aws.appautoscaling.inputs.PolicyTargetTrackingScalingPolicyConfigurationPredefinedMetricSpecificationArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var dynamodbTableReadTarget = new Target("dynamodbTableReadTarget", TargetArgs.builder()
            .maxCapacity(100)
            .minCapacity(5)
            .resourceId("table/tableName")
            .scalableDimension("dynamodb:table:ReadCapacityUnits")
            .serviceNamespace("dynamodb")
            .build());

        var dynamodbTableReadPolicy = new Policy("dynamodbTableReadPolicy", PolicyArgs.builder()
            .name(dynamodbTableReadTarget.resourceId().applyValue(_resourceId -> String.format("DynamoDBReadCapacityUtilization:%s", _resourceId)))
            .policyType("TargetTrackingScaling")
            .resourceId(dynamodbTableReadTarget.resourceId())
            .scalableDimension(dynamodbTableReadTarget.scalableDimension())
            .serviceNamespace(dynamodbTableReadTarget.serviceNamespace())
            .targetTrackingScalingPolicyConfiguration(PolicyTargetTrackingScalingPolicyConfigurationArgs.builder()
                .predefinedMetricSpecification(PolicyTargetTrackingScalingPolicyConfigurationPredefinedMetricSpecificationArgs.builder()
                    .predefinedMetricType("DynamoDBReadCapacityUtilization")
                    .build())
                .targetValue(70.0)
                .build())
            .build());

    }
}
resources:
  dynamodbTableReadTarget:
    type: aws:appautoscaling:Target
    name: dynamodb_table_read_target
    properties:
      maxCapacity: 100
      minCapacity: 5
      resourceId: table/tableName
      scalableDimension: dynamodb:table:ReadCapacityUnits
      serviceNamespace: dynamodb
  dynamodbTableReadPolicy:
    type: aws:appautoscaling:Policy
    name: dynamodb_table_read_policy
    properties:
      name: DynamoDBReadCapacityUtilization:${dynamodbTableReadTarget.resourceId}
      policyType: TargetTrackingScaling
      resourceId: ${dynamodbTableReadTarget.resourceId}
      scalableDimension: ${dynamodbTableReadTarget.scalableDimension}
      serviceNamespace: ${dynamodbTableReadTarget.serviceNamespace}
      targetTrackingScalingPolicyConfiguration:
        predefinedMetricSpecification:
          predefinedMetricType: DynamoDBReadCapacityUtilization
        targetValue: 70

When read capacity utilization exceeds 70%, Application Auto Scaling adds capacity units; when it drops below 70%, capacity is removed. The policyType of “TargetTrackingScaling” enables this behavior. The predefinedMetricSpecification uses AWS’s built-in DynamoDB read utilization metric, and targetValue sets the desired utilization level. The resourceId, scalableDimension, and serviceNamespace properties tie the policy to the Target resource that defines min and max capacity bounds.

Scale down ECS tasks with step scaling

ECS services running batch workloads often need to scale down quickly when load decreases. Step scaling policies trigger immediate capacity changes based on CloudWatch alarm thresholds.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const ecsTarget = new aws.appautoscaling.Target("ecs_target", {
    maxCapacity: 4,
    minCapacity: 1,
    resourceId: "service/clusterName/serviceName",
    scalableDimension: "ecs:service:DesiredCount",
    serviceNamespace: "ecs",
});
const ecsPolicy = new aws.appautoscaling.Policy("ecs_policy", {
    name: "scale-down",
    policyType: "StepScaling",
    resourceId: ecsTarget.resourceId,
    scalableDimension: ecsTarget.scalableDimension,
    serviceNamespace: ecsTarget.serviceNamespace,
    stepScalingPolicyConfiguration: {
        adjustmentType: "ChangeInCapacity",
        cooldown: 60,
        metricAggregationType: "Maximum",
        stepAdjustments: [{
            metricIntervalUpperBound: "0",
            scalingAdjustment: -1,
        }],
    },
});
import pulumi
import pulumi_aws as aws

ecs_target = aws.appautoscaling.Target("ecs_target",
    max_capacity=4,
    min_capacity=1,
    resource_id="service/clusterName/serviceName",
    scalable_dimension="ecs:service:DesiredCount",
    service_namespace="ecs")
ecs_policy = aws.appautoscaling.Policy("ecs_policy",
    name="scale-down",
    policy_type="StepScaling",
    resource_id=ecs_target.resource_id,
    scalable_dimension=ecs_target.scalable_dimension,
    service_namespace=ecs_target.service_namespace,
    step_scaling_policy_configuration={
        "adjustment_type": "ChangeInCapacity",
        "cooldown": 60,
        "metric_aggregation_type": "Maximum",
        "step_adjustments": [{
            "metric_interval_upper_bound": "0",
            "scaling_adjustment": -1,
        }],
    })
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/appautoscaling"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		ecsTarget, err := appautoscaling.NewTarget(ctx, "ecs_target", &appautoscaling.TargetArgs{
			MaxCapacity:       pulumi.Int(4),
			MinCapacity:       pulumi.Int(1),
			ResourceId:        pulumi.String("service/clusterName/serviceName"),
			ScalableDimension: pulumi.String("ecs:service:DesiredCount"),
			ServiceNamespace:  pulumi.String("ecs"),
		})
		if err != nil {
			return err
		}
		_, err = appautoscaling.NewPolicy(ctx, "ecs_policy", &appautoscaling.PolicyArgs{
			Name:              pulumi.String("scale-down"),
			PolicyType:        pulumi.String("StepScaling"),
			ResourceId:        ecsTarget.ResourceId,
			ScalableDimension: ecsTarget.ScalableDimension,
			ServiceNamespace:  ecsTarget.ServiceNamespace,
			StepScalingPolicyConfiguration: &appautoscaling.PolicyStepScalingPolicyConfigurationArgs{
				AdjustmentType:        pulumi.String("ChangeInCapacity"),
				Cooldown:              pulumi.Int(60),
				MetricAggregationType: pulumi.String("Maximum"),
				StepAdjustments: appautoscaling.PolicyStepScalingPolicyConfigurationStepAdjustmentArray{
					&appautoscaling.PolicyStepScalingPolicyConfigurationStepAdjustmentArgs{
						MetricIntervalUpperBound: pulumi.String("0"),
						ScalingAdjustment:        pulumi.Int(-1),
					},
				},
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var ecsTarget = new Aws.AppAutoScaling.Target("ecs_target", new()
    {
        MaxCapacity = 4,
        MinCapacity = 1,
        ResourceId = "service/clusterName/serviceName",
        ScalableDimension = "ecs:service:DesiredCount",
        ServiceNamespace = "ecs",
    });

    var ecsPolicy = new Aws.AppAutoScaling.Policy("ecs_policy", new()
    {
        Name = "scale-down",
        PolicyType = "StepScaling",
        ResourceId = ecsTarget.ResourceId,
        ScalableDimension = ecsTarget.ScalableDimension,
        ServiceNamespace = ecsTarget.ServiceNamespace,
        StepScalingPolicyConfiguration = new Aws.AppAutoScaling.Inputs.PolicyStepScalingPolicyConfigurationArgs
        {
            AdjustmentType = "ChangeInCapacity",
            Cooldown = 60,
            MetricAggregationType = "Maximum",
            StepAdjustments = new[]
            {
                new Aws.AppAutoScaling.Inputs.PolicyStepScalingPolicyConfigurationStepAdjustmentArgs
                {
                    MetricIntervalUpperBound = "0",
                    ScalingAdjustment = -1,
                },
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.appautoscaling.Target;
import com.pulumi.aws.appautoscaling.TargetArgs;
import com.pulumi.aws.appautoscaling.Policy;
import com.pulumi.aws.appautoscaling.PolicyArgs;
import com.pulumi.aws.appautoscaling.inputs.PolicyStepScalingPolicyConfigurationArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var ecsTarget = new Target("ecsTarget", TargetArgs.builder()
            .maxCapacity(4)
            .minCapacity(1)
            .resourceId("service/clusterName/serviceName")
            .scalableDimension("ecs:service:DesiredCount")
            .serviceNamespace("ecs")
            .build());

        var ecsPolicy = new Policy("ecsPolicy", PolicyArgs.builder()
            .name("scale-down")
            .policyType("StepScaling")
            .resourceId(ecsTarget.resourceId())
            .scalableDimension(ecsTarget.scalableDimension())
            .serviceNamespace(ecsTarget.serviceNamespace())
            .stepScalingPolicyConfiguration(PolicyStepScalingPolicyConfigurationArgs.builder()
                .adjustmentType("ChangeInCapacity")
                .cooldown(60)
                .metricAggregationType("Maximum")
                .stepAdjustments(PolicyStepScalingPolicyConfigurationStepAdjustmentArgs.builder()
                    .metricIntervalUpperBound("0")
                    .scalingAdjustment(-1)
                    .build())
                .build())
            .build());

    }
}
resources:
  ecsTarget:
    type: aws:appautoscaling:Target
    name: ecs_target
    properties:
      maxCapacity: 4
      minCapacity: 1
      resourceId: service/clusterName/serviceName
      scalableDimension: ecs:service:DesiredCount
      serviceNamespace: ecs
  ecsPolicy:
    type: aws:appautoscaling:Policy
    name: ecs_policy
    properties:
      name: scale-down
      policyType: StepScaling
      resourceId: ${ecsTarget.resourceId}
      scalableDimension: ${ecsTarget.scalableDimension}
      serviceNamespace: ${ecsTarget.serviceNamespace}
      stepScalingPolicyConfiguration:
        adjustmentType: ChangeInCapacity
        cooldown: 60
        metricAggregationType: Maximum
        stepAdjustments:
          - metricIntervalUpperBound: 0
            scalingAdjustment: -1

Step scaling responds to CloudWatch alarms by applying predefined adjustments. The stepScalingPolicyConfiguration defines how to adjust capacity: adjustmentType of “ChangeInCapacity” adds or removes a fixed number of tasks, while scalingAdjustment of -1 removes one task. The metricIntervalUpperBound of “0” means this adjustment applies when the metric is below the alarm threshold. The cooldown period prevents rapid successive scaling actions.

Scale Aurora read replicas based on CPU

Aurora clusters distribute read traffic across replicas to handle query load. When CPU utilization rises, adding replicas spreads the load and maintains response times.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const replicas = new aws.appautoscaling.Target("replicas", {
    serviceNamespace: "rds",
    scalableDimension: "rds:cluster:ReadReplicaCount",
    resourceId: `cluster:${example.id}`,
    minCapacity: 1,
    maxCapacity: 15,
});
const replicasPolicy = new aws.appautoscaling.Policy("replicas", {
    name: "cpu-auto-scaling",
    serviceNamespace: replicas.serviceNamespace,
    scalableDimension: replicas.scalableDimension,
    resourceId: replicas.resourceId,
    policyType: "TargetTrackingScaling",
    targetTrackingScalingPolicyConfiguration: {
        predefinedMetricSpecification: {
            predefinedMetricType: "RDSReaderAverageCPUUtilization",
        },
        targetValue: 75,
        scaleInCooldown: 300,
        scaleOutCooldown: 300,
    },
});
import pulumi
import pulumi_aws as aws

replicas = aws.appautoscaling.Target("replicas",
    service_namespace="rds",
    scalable_dimension="rds:cluster:ReadReplicaCount",
    resource_id=f"cluster:{example['id']}",
    min_capacity=1,
    max_capacity=15)
replicas_policy = aws.appautoscaling.Policy("replicas",
    name="cpu-auto-scaling",
    service_namespace=replicas.service_namespace,
    scalable_dimension=replicas.scalable_dimension,
    resource_id=replicas.resource_id,
    policy_type="TargetTrackingScaling",
    target_tracking_scaling_policy_configuration={
        "predefined_metric_specification": {
            "predefined_metric_type": "RDSReaderAverageCPUUtilization",
        },
        "target_value": 75,
        "scale_in_cooldown": 300,
        "scale_out_cooldown": 300,
    })
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/appautoscaling"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		replicas, err := appautoscaling.NewTarget(ctx, "replicas", &appautoscaling.TargetArgs{
			ServiceNamespace:  pulumi.String("rds"),
			ScalableDimension: pulumi.String("rds:cluster:ReadReplicaCount"),
			ResourceId:        pulumi.Sprintf("cluster:%v", example.Id),
			MinCapacity:       pulumi.Int(1),
			MaxCapacity:       pulumi.Int(15),
		})
		if err != nil {
			return err
		}
		_, err = appautoscaling.NewPolicy(ctx, "replicas", &appautoscaling.PolicyArgs{
			Name:              pulumi.String("cpu-auto-scaling"),
			ServiceNamespace:  replicas.ServiceNamespace,
			ScalableDimension: replicas.ScalableDimension,
			ResourceId:        replicas.ResourceId,
			PolicyType:        pulumi.String("TargetTrackingScaling"),
			TargetTrackingScalingPolicyConfiguration: &appautoscaling.PolicyTargetTrackingScalingPolicyConfigurationArgs{
				PredefinedMetricSpecification: &appautoscaling.PolicyTargetTrackingScalingPolicyConfigurationPredefinedMetricSpecificationArgs{
					PredefinedMetricType: pulumi.String("RDSReaderAverageCPUUtilization"),
				},
				TargetValue:      pulumi.Float64(75),
				ScaleInCooldown:  pulumi.Int(300),
				ScaleOutCooldown: pulumi.Int(300),
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var replicas = new Aws.AppAutoScaling.Target("replicas", new()
    {
        ServiceNamespace = "rds",
        ScalableDimension = "rds:cluster:ReadReplicaCount",
        ResourceId = $"cluster:{example.Id}",
        MinCapacity = 1,
        MaxCapacity = 15,
    });

    var replicasPolicy = new Aws.AppAutoScaling.Policy("replicas", new()
    {
        Name = "cpu-auto-scaling",
        ServiceNamespace = replicas.ServiceNamespace,
        ScalableDimension = replicas.ScalableDimension,
        ResourceId = replicas.ResourceId,
        PolicyType = "TargetTrackingScaling",
        TargetTrackingScalingPolicyConfiguration = new Aws.AppAutoScaling.Inputs.PolicyTargetTrackingScalingPolicyConfigurationArgs
        {
            PredefinedMetricSpecification = new Aws.AppAutoScaling.Inputs.PolicyTargetTrackingScalingPolicyConfigurationPredefinedMetricSpecificationArgs
            {
                PredefinedMetricType = "RDSReaderAverageCPUUtilization",
            },
            TargetValue = 75,
            ScaleInCooldown = 300,
            ScaleOutCooldown = 300,
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.appautoscaling.Target;
import com.pulumi.aws.appautoscaling.TargetArgs;
import com.pulumi.aws.appautoscaling.Policy;
import com.pulumi.aws.appautoscaling.PolicyArgs;
import com.pulumi.aws.appautoscaling.inputs.PolicyTargetTrackingScalingPolicyConfigurationArgs;
import com.pulumi.aws.appautoscaling.inputs.PolicyTargetTrackingScalingPolicyConfigurationPredefinedMetricSpecificationArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var replicas = new Target("replicas", TargetArgs.builder()
            .serviceNamespace("rds")
            .scalableDimension("rds:cluster:ReadReplicaCount")
            .resourceId(String.format("cluster:%s", example.id()))
            .minCapacity(1)
            .maxCapacity(15)
            .build());

        var replicasPolicy = new Policy("replicasPolicy", PolicyArgs.builder()
            .name("cpu-auto-scaling")
            .serviceNamespace(replicas.serviceNamespace())
            .scalableDimension(replicas.scalableDimension())
            .resourceId(replicas.resourceId())
            .policyType("TargetTrackingScaling")
            .targetTrackingScalingPolicyConfiguration(PolicyTargetTrackingScalingPolicyConfigurationArgs.builder()
                .predefinedMetricSpecification(PolicyTargetTrackingScalingPolicyConfigurationPredefinedMetricSpecificationArgs.builder()
                    .predefinedMetricType("RDSReaderAverageCPUUtilization")
                    .build())
                .targetValue(75.0)
                .scaleInCooldown(300)
                .scaleOutCooldown(300)
                .build())
            .build());

    }
}
resources:
  replicas:
    type: aws:appautoscaling:Target
    properties:
      serviceNamespace: rds
      scalableDimension: rds:cluster:ReadReplicaCount
      resourceId: cluster:${example.id}
      minCapacity: 1
      maxCapacity: 15
  replicasPolicy:
    type: aws:appautoscaling:Policy
    name: replicas
    properties:
      name: cpu-auto-scaling
      serviceNamespace: ${replicas.serviceNamespace}
      scalableDimension: ${replicas.scalableDimension}
      resourceId: ${replicas.resourceId}
      policyType: TargetTrackingScaling
      targetTrackingScalingPolicyConfiguration:
        predefinedMetricSpecification:
          predefinedMetricType: RDSReaderAverageCPUUtilization
        targetValue: 75
        scaleInCooldown: 300
        scaleOutCooldown: 300

This policy scales Aurora read replicas to maintain 75% CPU utilization across the cluster. The serviceNamespace of “rds” and scalableDimension of “rds:cluster:ReadReplicaCount” specify that this policy controls replica count. The scaleInCooldown and scaleOutCooldown properties (both 300 seconds) prevent thrashing by enforcing wait periods between scaling actions.

Calculate backlog per task with metric math

Applications processing queues need to scale based on backlog per worker, not just queue depth. Metric math expressions combine multiple CloudWatch metrics into derived values that better represent actual load.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const ecsTarget = new aws.appautoscaling.Target("ecs_target", {
    maxCapacity: 4,
    minCapacity: 1,
    resourceId: "service/clusterName/serviceName",
    scalableDimension: "ecs:service:DesiredCount",
    serviceNamespace: "ecs",
});
const example = new aws.appautoscaling.Policy("example", {
    name: "foo",
    policyType: "TargetTrackingScaling",
    resourceId: ecsTarget.resourceId,
    scalableDimension: ecsTarget.scalableDimension,
    serviceNamespace: ecsTarget.serviceNamespace,
    targetTrackingScalingPolicyConfiguration: {
        targetValue: 100,
        customizedMetricSpecification: {
            metrics: [
                {
                    label: "Get the queue size (the number of messages waiting to be processed)",
                    id: "m1",
                    metricStat: {
                        metric: {
                            metricName: "ApproximateNumberOfMessagesVisible",
                            namespace: "AWS/SQS",
                            dimensions: [{
                                name: "QueueName",
                                value: "my-queue",
                            }],
                        },
                        stat: "Sum",
                    },
                    returnData: false,
                },
                {
                    label: "Get the ECS running task count (the number of currently running tasks)",
                    id: "m2",
                    metricStat: {
                        metric: {
                            metricName: "RunningTaskCount",
                            namespace: "ECS/ContainerInsights",
                            dimensions: [
                                {
                                    name: "ClusterName",
                                    value: "default",
                                },
                                {
                                    name: "ServiceName",
                                    value: "web-app",
                                },
                            ],
                        },
                        stat: "Average",
                    },
                    returnData: false,
                },
                {
                    label: "Calculate the backlog per instance",
                    id: "e1",
                    expression: "m1 / m2",
                    returnData: true,
                },
            ],
        },
    },
});
import pulumi
import pulumi_aws as aws

ecs_target = aws.appautoscaling.Target("ecs_target",
    max_capacity=4,
    min_capacity=1,
    resource_id="service/clusterName/serviceName",
    scalable_dimension="ecs:service:DesiredCount",
    service_namespace="ecs")
example = aws.appautoscaling.Policy("example",
    name="foo",
    policy_type="TargetTrackingScaling",
    resource_id=ecs_target.resource_id,
    scalable_dimension=ecs_target.scalable_dimension,
    service_namespace=ecs_target.service_namespace,
    target_tracking_scaling_policy_configuration={
        "target_value": 100,
        "customized_metric_specification": {
            "metrics": [
                {
                    "label": "Get the queue size (the number of messages waiting to be processed)",
                    "id": "m1",
                    "metric_stat": {
                        "metric": {
                            "metric_name": "ApproximateNumberOfMessagesVisible",
                            "namespace": "AWS/SQS",
                            "dimensions": [{
                                "name": "QueueName",
                                "value": "my-queue",
                            }],
                        },
                        "stat": "Sum",
                    },
                    "return_data": False,
                },
                {
                    "label": "Get the ECS running task count (the number of currently running tasks)",
                    "id": "m2",
                    "metric_stat": {
                        "metric": {
                            "metric_name": "RunningTaskCount",
                            "namespace": "ECS/ContainerInsights",
                            "dimensions": [
                                {
                                    "name": "ClusterName",
                                    "value": "default",
                                },
                                {
                                    "name": "ServiceName",
                                    "value": "web-app",
                                },
                            ],
                        },
                        "stat": "Average",
                    },
                    "return_data": False,
                },
                {
                    "label": "Calculate the backlog per instance",
                    "id": "e1",
                    "expression": "m1 / m2",
                    "return_data": True,
                },
            ],
        },
    })
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/appautoscaling"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		ecsTarget, err := appautoscaling.NewTarget(ctx, "ecs_target", &appautoscaling.TargetArgs{
			MaxCapacity:       pulumi.Int(4),
			MinCapacity:       pulumi.Int(1),
			ResourceId:        pulumi.String("service/clusterName/serviceName"),
			ScalableDimension: pulumi.String("ecs:service:DesiredCount"),
			ServiceNamespace:  pulumi.String("ecs"),
		})
		if err != nil {
			return err
		}
		_, err = appautoscaling.NewPolicy(ctx, "example", &appautoscaling.PolicyArgs{
			Name:              pulumi.String("foo"),
			PolicyType:        pulumi.String("TargetTrackingScaling"),
			ResourceId:        ecsTarget.ResourceId,
			ScalableDimension: ecsTarget.ScalableDimension,
			ServiceNamespace:  ecsTarget.ServiceNamespace,
			TargetTrackingScalingPolicyConfiguration: &appautoscaling.PolicyTargetTrackingScalingPolicyConfigurationArgs{
				TargetValue: pulumi.Float64(100),
				CustomizedMetricSpecification: &appautoscaling.PolicyTargetTrackingScalingPolicyConfigurationCustomizedMetricSpecificationArgs{
					Metrics: appautoscaling.PolicyTargetTrackingScalingPolicyConfigurationCustomizedMetricSpecificationMetricArray{
						&appautoscaling.PolicyTargetTrackingScalingPolicyConfigurationCustomizedMetricSpecificationMetricArgs{
							Label: pulumi.String("Get the queue size (the number of messages waiting to be processed)"),
							Id:    pulumi.String("m1"),
							MetricStat: &appautoscaling.PolicyTargetTrackingScalingPolicyConfigurationCustomizedMetricSpecificationMetricMetricStatArgs{
								Metric: &appautoscaling.PolicyTargetTrackingScalingPolicyConfigurationCustomizedMetricSpecificationMetricMetricStatMetricArgs{
									MetricName: pulumi.String("ApproximateNumberOfMessagesVisible"),
									Namespace:  pulumi.String("AWS/SQS"),
									Dimensions: appautoscaling.PolicyTargetTrackingScalingPolicyConfigurationCustomizedMetricSpecificationMetricMetricStatMetricDimensionArray{
										&appautoscaling.PolicyTargetTrackingScalingPolicyConfigurationCustomizedMetricSpecificationMetricMetricStatMetricDimensionArgs{
											Name:  pulumi.String("QueueName"),
											Value: pulumi.String("my-queue"),
										},
									},
								},
								Stat: pulumi.String("Sum"),
							},
							ReturnData: pulumi.Bool(false),
						},
						&appautoscaling.PolicyTargetTrackingScalingPolicyConfigurationCustomizedMetricSpecificationMetricArgs{
							Label: pulumi.String("Get the ECS running task count (the number of currently running tasks)"),
							Id:    pulumi.String("m2"),
							MetricStat: &appautoscaling.PolicyTargetTrackingScalingPolicyConfigurationCustomizedMetricSpecificationMetricMetricStatArgs{
								Metric: &appautoscaling.PolicyTargetTrackingScalingPolicyConfigurationCustomizedMetricSpecificationMetricMetricStatMetricArgs{
									MetricName: pulumi.String("RunningTaskCount"),
									Namespace:  pulumi.String("ECS/ContainerInsights"),
									Dimensions: appautoscaling.PolicyTargetTrackingScalingPolicyConfigurationCustomizedMetricSpecificationMetricMetricStatMetricDimensionArray{
										&appautoscaling.PolicyTargetTrackingScalingPolicyConfigurationCustomizedMetricSpecificationMetricMetricStatMetricDimensionArgs{
											Name:  pulumi.String("ClusterName"),
											Value: pulumi.String("default"),
										},
										&appautoscaling.PolicyTargetTrackingScalingPolicyConfigurationCustomizedMetricSpecificationMetricMetricStatMetricDimensionArgs{
											Name:  pulumi.String("ServiceName"),
											Value: pulumi.String("web-app"),
										},
									},
								},
								Stat: pulumi.String("Average"),
							},
							ReturnData: pulumi.Bool(false),
						},
						&appautoscaling.PolicyTargetTrackingScalingPolicyConfigurationCustomizedMetricSpecificationMetricArgs{
							Label:      pulumi.String("Calculate the backlog per instance"),
							Id:         pulumi.String("e1"),
							Expression: pulumi.String("m1 / m2"),
							ReturnData: pulumi.Bool(true),
						},
					},
				},
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var ecsTarget = new Aws.AppAutoScaling.Target("ecs_target", new()
    {
        MaxCapacity = 4,
        MinCapacity = 1,
        ResourceId = "service/clusterName/serviceName",
        ScalableDimension = "ecs:service:DesiredCount",
        ServiceNamespace = "ecs",
    });

    var example = new Aws.AppAutoScaling.Policy("example", new()
    {
        Name = "foo",
        PolicyType = "TargetTrackingScaling",
        ResourceId = ecsTarget.ResourceId,
        ScalableDimension = ecsTarget.ScalableDimension,
        ServiceNamespace = ecsTarget.ServiceNamespace,
        TargetTrackingScalingPolicyConfiguration = new Aws.AppAutoScaling.Inputs.PolicyTargetTrackingScalingPolicyConfigurationArgs
        {
            TargetValue = 100,
            CustomizedMetricSpecification = new Aws.AppAutoScaling.Inputs.PolicyTargetTrackingScalingPolicyConfigurationCustomizedMetricSpecificationArgs
            {
                Metrics = new[]
                {
                    new Aws.AppAutoScaling.Inputs.PolicyTargetTrackingScalingPolicyConfigurationCustomizedMetricSpecificationMetricArgs
                    {
                        Label = "Get the queue size (the number of messages waiting to be processed)",
                        Id = "m1",
                        MetricStat = new Aws.AppAutoScaling.Inputs.PolicyTargetTrackingScalingPolicyConfigurationCustomizedMetricSpecificationMetricMetricStatArgs
                        {
                            Metric = new Aws.AppAutoScaling.Inputs.PolicyTargetTrackingScalingPolicyConfigurationCustomizedMetricSpecificationMetricMetricStatMetricArgs
                            {
                                MetricName = "ApproximateNumberOfMessagesVisible",
                                Namespace = "AWS/SQS",
                                Dimensions = new[]
                                {
                                    new Aws.AppAutoScaling.Inputs.PolicyTargetTrackingScalingPolicyConfigurationCustomizedMetricSpecificationMetricMetricStatMetricDimensionArgs
                                    {
                                        Name = "QueueName",
                                        Value = "my-queue",
                                    },
                                },
                            },
                            Stat = "Sum",
                        },
                        ReturnData = false,
                    },
                    new Aws.AppAutoScaling.Inputs.PolicyTargetTrackingScalingPolicyConfigurationCustomizedMetricSpecificationMetricArgs
                    {
                        Label = "Get the ECS running task count (the number of currently running tasks)",
                        Id = "m2",
                        MetricStat = new Aws.AppAutoScaling.Inputs.PolicyTargetTrackingScalingPolicyConfigurationCustomizedMetricSpecificationMetricMetricStatArgs
                        {
                            Metric = new Aws.AppAutoScaling.Inputs.PolicyTargetTrackingScalingPolicyConfigurationCustomizedMetricSpecificationMetricMetricStatMetricArgs
                            {
                                MetricName = "RunningTaskCount",
                                Namespace = "ECS/ContainerInsights",
                                Dimensions = new[]
                                {
                                    new Aws.AppAutoScaling.Inputs.PolicyTargetTrackingScalingPolicyConfigurationCustomizedMetricSpecificationMetricMetricStatMetricDimensionArgs
                                    {
                                        Name = "ClusterName",
                                        Value = "default",
                                    },
                                    new Aws.AppAutoScaling.Inputs.PolicyTargetTrackingScalingPolicyConfigurationCustomizedMetricSpecificationMetricMetricStatMetricDimensionArgs
                                    {
                                        Name = "ServiceName",
                                        Value = "web-app",
                                    },
                                },
                            },
                            Stat = "Average",
                        },
                        ReturnData = false,
                    },
                    new Aws.AppAutoScaling.Inputs.PolicyTargetTrackingScalingPolicyConfigurationCustomizedMetricSpecificationMetricArgs
                    {
                        Label = "Calculate the backlog per instance",
                        Id = "e1",
                        Expression = "m1 / m2",
                        ReturnData = true,
                    },
                },
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.appautoscaling.Target;
import com.pulumi.aws.appautoscaling.TargetArgs;
import com.pulumi.aws.appautoscaling.Policy;
import com.pulumi.aws.appautoscaling.PolicyArgs;
import com.pulumi.aws.appautoscaling.inputs.PolicyTargetTrackingScalingPolicyConfigurationArgs;
import com.pulumi.aws.appautoscaling.inputs.PolicyTargetTrackingScalingPolicyConfigurationCustomizedMetricSpecificationArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var ecsTarget = new Target("ecsTarget", TargetArgs.builder()
            .maxCapacity(4)
            .minCapacity(1)
            .resourceId("service/clusterName/serviceName")
            .scalableDimension("ecs:service:DesiredCount")
            .serviceNamespace("ecs")
            .build());

        var example = new Policy("example", PolicyArgs.builder()
            .name("foo")
            .policyType("TargetTrackingScaling")
            .resourceId(ecsTarget.resourceId())
            .scalableDimension(ecsTarget.scalableDimension())
            .serviceNamespace(ecsTarget.serviceNamespace())
            .targetTrackingScalingPolicyConfiguration(PolicyTargetTrackingScalingPolicyConfigurationArgs.builder()
                .targetValue(100.0)
                .customizedMetricSpecification(PolicyTargetTrackingScalingPolicyConfigurationCustomizedMetricSpecificationArgs.builder()
                    .metrics(                    
                        PolicyTargetTrackingScalingPolicyConfigurationCustomizedMetricSpecificationMetricArgs.builder()
                            .label("Get the queue size (the number of messages waiting to be processed)")
                            .id("m1")
                            .metricStat(PolicyTargetTrackingScalingPolicyConfigurationCustomizedMetricSpecificationMetricMetricStatArgs.builder()
                                .metric(PolicyTargetTrackingScalingPolicyConfigurationCustomizedMetricSpecificationMetricMetricStatMetricArgs.builder()
                                    .metricName("ApproximateNumberOfMessagesVisible")
                                    .namespace("AWS/SQS")
                                    .dimensions(PolicyTargetTrackingScalingPolicyConfigurationCustomizedMetricSpecificationMetricMetricStatMetricDimensionArgs.builder()
                                        .name("QueueName")
                                        .value("my-queue")
                                        .build())
                                    .build())
                                .stat("Sum")
                                .build())
                            .returnData(false)
                            .build(),
                        PolicyTargetTrackingScalingPolicyConfigurationCustomizedMetricSpecificationMetricArgs.builder()
                            .label("Get the ECS running task count (the number of currently running tasks)")
                            .id("m2")
                            .metricStat(PolicyTargetTrackingScalingPolicyConfigurationCustomizedMetricSpecificationMetricMetricStatArgs.builder()
                                .metric(PolicyTargetTrackingScalingPolicyConfigurationCustomizedMetricSpecificationMetricMetricStatMetricArgs.builder()
                                    .metricName("RunningTaskCount")
                                    .namespace("ECS/ContainerInsights")
                                    .dimensions(                                    
                                        PolicyTargetTrackingScalingPolicyConfigurationCustomizedMetricSpecificationMetricMetricStatMetricDimensionArgs.builder()
                                            .name("ClusterName")
                                            .value("default")
                                            .build(),
                                        PolicyTargetTrackingScalingPolicyConfigurationCustomizedMetricSpecificationMetricMetricStatMetricDimensionArgs.builder()
                                            .name("ServiceName")
                                            .value("web-app")
                                            .build())
                                    .build())
                                .stat("Average")
                                .build())
                            .returnData(false)
                            .build(),
                        PolicyTargetTrackingScalingPolicyConfigurationCustomizedMetricSpecificationMetricArgs.builder()
                            .label("Calculate the backlog per instance")
                            .id("e1")
                            .expression("m1 / m2")
                            .returnData(true)
                            .build())
                    .build())
                .build())
            .build());

    }
}
resources:
  ecsTarget:
    type: aws:appautoscaling:Target
    name: ecs_target
    properties:
      maxCapacity: 4
      minCapacity: 1
      resourceId: service/clusterName/serviceName
      scalableDimension: ecs:service:DesiredCount
      serviceNamespace: ecs
  example:
    type: aws:appautoscaling:Policy
    properties:
      name: foo
      policyType: TargetTrackingScaling
      resourceId: ${ecsTarget.resourceId}
      scalableDimension: ${ecsTarget.scalableDimension}
      serviceNamespace: ${ecsTarget.serviceNamespace}
      targetTrackingScalingPolicyConfiguration:
        targetValue: 100
        customizedMetricSpecification:
          metrics:
            - label: Get the queue size (the number of messages waiting to be processed)
              id: m1
              metricStat:
                metric:
                  metricName: ApproximateNumberOfMessagesVisible
                  namespace: AWS/SQS
                  dimensions:
                    - name: QueueName
                      value: my-queue
                stat: Sum
              returnData: false
            - label: Get the ECS running task count (the number of currently running tasks)
              id: m2
              metricStat:
                metric:
                  metricName: RunningTaskCount
                  namespace: ECS/ContainerInsights
                  dimensions:
                    - name: ClusterName
                      value: default
                    - name: ServiceName
                      value: web-app
                stat: Average
              returnData: false
            - label: Calculate the backlog per instance
              id: e1
              expression: m1 / m2
              returnData: true

The customizedMetricSpecification defines a metric math expression that divides SQS queue size by running ECS task count. Each metric in the metrics array has an id for reference: m1 fetches queue depth, m2 fetches task count, and e1 calculates the ratio. Only the expression (e1) has returnData set to true, making it the value the policy tracks. This ensures scaling maintains a target number of messages per task rather than a fixed task count.

Use predictive scaling for ECS memory patterns

Workloads with predictable daily or weekly patterns can benefit from predictive scaling, which forecasts future load and scales proactively rather than reactively.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const example = new aws.appautoscaling.Policy("example", {
    name: "example-policy",
    resourceId: exampleAwsAppautoscalingTarget.resourceId,
    scalableDimension: exampleAwsAppautoscalingTarget.scalableDimension,
    serviceNamespace: exampleAwsAppautoscalingTarget.serviceNamespace,
    policyType: "PredictiveScaling",
    predictiveScalingPolicyConfiguration: {
        metricSpecifications: [{
            targetValue: "40",
            predefinedMetricPairSpecification: {
                predefinedMetricType: "ECSServiceMemoryUtilization",
            },
        }],
    },
});
import pulumi
import pulumi_aws as aws

example = aws.appautoscaling.Policy("example",
    name="example-policy",
    resource_id=example_aws_appautoscaling_target["resourceId"],
    scalable_dimension=example_aws_appautoscaling_target["scalableDimension"],
    service_namespace=example_aws_appautoscaling_target["serviceNamespace"],
    policy_type="PredictiveScaling",
    predictive_scaling_policy_configuration={
        "metric_specifications": [{
            "target_value": "40",
            "predefined_metric_pair_specification": {
                "predefined_metric_type": "ECSServiceMemoryUtilization",
            },
        }],
    })
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/appautoscaling"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := appautoscaling.NewPolicy(ctx, "example", &appautoscaling.PolicyArgs{
			Name:              pulumi.String("example-policy"),
			ResourceId:        pulumi.Any(exampleAwsAppautoscalingTarget.ResourceId),
			ScalableDimension: pulumi.Any(exampleAwsAppautoscalingTarget.ScalableDimension),
			ServiceNamespace:  pulumi.Any(exampleAwsAppautoscalingTarget.ServiceNamespace),
			PolicyType:        pulumi.String("PredictiveScaling"),
			PredictiveScalingPolicyConfiguration: &appautoscaling.PolicyPredictiveScalingPolicyConfigurationArgs{
				MetricSpecifications: appautoscaling.PolicyPredictiveScalingPolicyConfigurationMetricSpecificationArray{
					&appautoscaling.PolicyPredictiveScalingPolicyConfigurationMetricSpecificationArgs{
						TargetValue: pulumi.String("40"),
						PredefinedMetricPairSpecification: &appautoscaling.PolicyPredictiveScalingPolicyConfigurationMetricSpecificationPredefinedMetricPairSpecificationArgs{
							PredefinedMetricType: pulumi.String("ECSServiceMemoryUtilization"),
						},
					},
				},
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var example = new Aws.AppAutoScaling.Policy("example", new()
    {
        Name = "example-policy",
        ResourceId = exampleAwsAppautoscalingTarget.ResourceId,
        ScalableDimension = exampleAwsAppautoscalingTarget.ScalableDimension,
        ServiceNamespace = exampleAwsAppautoscalingTarget.ServiceNamespace,
        PolicyType = "PredictiveScaling",
        PredictiveScalingPolicyConfiguration = new Aws.AppAutoScaling.Inputs.PolicyPredictiveScalingPolicyConfigurationArgs
        {
            MetricSpecifications = new[]
            {
                new Aws.AppAutoScaling.Inputs.PolicyPredictiveScalingPolicyConfigurationMetricSpecificationArgs
                {
                    TargetValue = "40",
                    PredefinedMetricPairSpecification = new Aws.AppAutoScaling.Inputs.PolicyPredictiveScalingPolicyConfigurationMetricSpecificationPredefinedMetricPairSpecificationArgs
                    {
                        PredefinedMetricType = "ECSServiceMemoryUtilization",
                    },
                },
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.appautoscaling.Policy;
import com.pulumi.aws.appautoscaling.PolicyArgs;
import com.pulumi.aws.appautoscaling.inputs.PolicyPredictiveScalingPolicyConfigurationArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var example = new Policy("example", PolicyArgs.builder()
            .name("example-policy")
            .resourceId(exampleAwsAppautoscalingTarget.resourceId())
            .scalableDimension(exampleAwsAppautoscalingTarget.scalableDimension())
            .serviceNamespace(exampleAwsAppautoscalingTarget.serviceNamespace())
            .policyType("PredictiveScaling")
            .predictiveScalingPolicyConfiguration(PolicyPredictiveScalingPolicyConfigurationArgs.builder()
                .metricSpecifications(PolicyPredictiveScalingPolicyConfigurationMetricSpecificationArgs.builder()
                    .targetValue("40")
                    .predefinedMetricPairSpecification(PolicyPredictiveScalingPolicyConfigurationMetricSpecificationPredefinedMetricPairSpecificationArgs.builder()
                        .predefinedMetricType("ECSServiceMemoryUtilization")
                        .build())
                    .build())
                .build())
            .build());

    }
}
resources:
  example:
    type: aws:appautoscaling:Policy
    properties:
      name: example-policy
      resourceId: ${exampleAwsAppautoscalingTarget.resourceId}
      scalableDimension: ${exampleAwsAppautoscalingTarget.scalableDimension}
      serviceNamespace: ${exampleAwsAppautoscalingTarget.serviceNamespace}
      policyType: PredictiveScaling
      predictiveScalingPolicyConfiguration:
        metricSpecifications:
          - targetValue: 40
            predefinedMetricPairSpecification:
              predefinedMetricType: ECSServiceMemoryUtilization

Predictive scaling analyzes historical metric data to forecast future load and scales ahead of predicted spikes. The policyType of “PredictiveScaling” enables this behavior. The metricSpecifications array defines what to forecast: predefinedMetricPairSpecification uses AWS’s built-in ECS memory utilization metric pair (load and scaling metrics), and targetValue sets the desired utilization level. Application Auto Scaling generates forecasts and schedules scaling actions to meet the target before load arrives.

Beyond these examples

These snippets focus on specific policy-level features: target tracking with predefined and custom metrics, step scaling for immediate adjustments, and predictive scaling for forecasted load. They’re intentionally minimal rather than full auto-scaling solutions.

The examples rely on pre-existing infrastructure such as appautoscaling.Target resources defining capacity bounds, DynamoDB tables, ECS services, Aurora clusters, MSK clusters, and CloudWatch alarms for step scaling policies. They focus on configuring the policy rather than provisioning the services being scaled.

To keep things focused, common policy patterns are omitted, including:

  • Cooldown periods and adjustment magnitude tuning
  • Scale-in protection and suspension
  • Multiple step adjustments for graduated scaling
  • Custom metric specifications with dimensions

These omissions are intentional: the goal is to illustrate how each policy feature is wired, not provide drop-in auto-scaling modules. See the Application Auto Scaling Policy resource reference for all available configuration options.

Let's configure AWS Application Auto Scaling Policies

Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.

Try Pulumi Cloud for FREE

Frequently Asked Questions

Policy Configuration & Types
What are the available policy types and which is default?
Three policy types are available: StepScaling (default), TargetTrackingScaling, and PredictiveScaling. Each requires a corresponding configuration block (stepScalingPolicyConfiguration, targetTrackingScalingPolicyConfiguration, or predictiveScalingPolicyConfiguration).
What properties are immutable after creation?
The name, resourceId, scalableDimension, and serviceNamespace properties cannot be changed after creation. Modifying any of these forces resource replacement.
How do I choose between StepScaling and TargetTrackingScaling?
Use TargetTrackingScaling to maintain a specific metric target (e.g., 70% CPU utilization). Use StepScaling for more granular control with step adjustments based on metric thresholds. Note that certain services only support one policy type.
Metrics & Scaling Behavior
How do I use custom metrics with metric math?
Configure customizedMetricSpecification with a metrics array. Include metricStat entries for data sources and expression entries for calculations (e.g., "m1 / m2" to calculate backlog per instance). Set returnData: true on the final expression.
How do I set cooldown periods for scaling actions?
For TargetTrackingScaling policies, configure scaleInCooldown and scaleOutCooldown (in seconds) within targetTrackingScalingPolicyConfiguration. This controls the wait time between scaling activities.
What's the difference between predefined and custom metric specifications?
Predefined metrics use predefinedMetricSpecification with a predefinedMetricType (e.g., DynamoDBReadCapacityUtilization). Custom metrics use customizedMetricSpecification to define your own CloudWatch metrics or metric math expressions.
Service-Specific Usage
Which AWS services support Application AutoScaling policies?
Examples include DynamoDB (dynamodb namespace), ECS (ecs namespace), RDS/Aurora (rds namespace), and MSK/Kafka (kafka namespace). Each service uses specific scalableDimension values like dynamodb:table:ReadCapacityUnits or ecs:service:DesiredCount.
How do I configure autoscaling for DynamoDB table capacity?
Set serviceNamespace to dynamodb, scalableDimension to dynamodb:table:ReadCapacityUnits or dynamodb:table:WriteCapacityUnits, and use TargetTrackingScaling with predefinedMetricType of DynamoDBReadCapacityUtilization or DynamoDBWriteCapacityUtilization.
How do I configure autoscaling for ECS service task counts?
Set serviceNamespace to ecs, scalableDimension to ecs:service:DesiredCount, and resourceId to service/clusterName/serviceName. You can use either StepScaling or TargetTrackingScaling policy types.

Using a different cloud?

Explore compute guides for other cloud providers: