Configure AWS Application Auto Scaling Targets

The aws:appautoscaling/target:Target resource, part of the Pulumi AWS provider, registers AWS resources as scalable targets, defining their capacity bounds and enabling Application AutoScaling to adjust them. This guide focuses on four capabilities: DynamoDB table and index capacity, ECS service task counts, Aurora read replicas, and MSK broker storage.

Scalable targets reference existing AWS resources and work with aws.appautoscaling.Policy resources that define when to scale. The examples are intentionally small. Combine them with your own scaling policies and CloudWatch alarms.

Scale DynamoDB table read capacity

Applications with variable read traffic often need DynamoDB tables that scale capacity automatically based on demand.

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/${example.name}`,
    scalableDimension: "dynamodb:table:ReadCapacityUnits",
    serviceNamespace: "dynamodb",
});
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=f"table/{example['name']}",
    scalable_dimension="dynamodb:table:ReadCapacityUnits",
    service_namespace="dynamodb")
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 {
		_, err := appautoscaling.NewTarget(ctx, "dynamodb_table_read_target", &appautoscaling.TargetArgs{
			MaxCapacity:       pulumi.Int(100),
			MinCapacity:       pulumi.Int(5),
			ResourceId:        pulumi.Sprintf("table/%v", example.Name),
			ScalableDimension: pulumi.String("dynamodb:table:ReadCapacityUnits"),
			ServiceNamespace:  pulumi.String("dynamodb"),
		})
		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/{example.Name}",
        ScalableDimension = "dynamodb:table:ReadCapacityUnits",
        ServiceNamespace = "dynamodb",
    });

});
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 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(String.format("table/%s", example.name()))
            .scalableDimension("dynamodb:table:ReadCapacityUnits")
            .serviceNamespace("dynamodb")
            .build());

    }
}
resources:
  dynamodbTableReadTarget:
    type: aws:appautoscaling:Target
    name: dynamodb_table_read_target
    properties:
      maxCapacity: 100
      minCapacity: 5
      resourceId: table/${example.name}
      scalableDimension: dynamodb:table:ReadCapacityUnits
      serviceNamespace: dynamodb

The resourceId property identifies the table using the format table/{table-name}. The scalableDimension specifies which metric to scale (here, read capacity units). Application AutoScaling adjusts capacity between minCapacity and maxCapacity based on policies you attach separately. The serviceNamespace tells Application AutoScaling this is a DynamoDB resource.

Scale DynamoDB global secondary index capacity

Global secondary indexes often have different traffic patterns than their base tables, requiring independent capacity management.

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

const dynamodbIndexReadTarget = new aws.appautoscaling.Target("dynamodb_index_read_target", {
    maxCapacity: 100,
    minCapacity: 5,
    resourceId: `table/${example.name}/index/${indexName}`,
    scalableDimension: "dynamodb:index:ReadCapacityUnits",
    serviceNamespace: "dynamodb",
});
import pulumi
import pulumi_aws as aws

dynamodb_index_read_target = aws.appautoscaling.Target("dynamodb_index_read_target",
    max_capacity=100,
    min_capacity=5,
    resource_id=f"table/{example['name']}/index/{index_name}",
    scalable_dimension="dynamodb:index:ReadCapacityUnits",
    service_namespace="dynamodb")
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 {
		_, err := appautoscaling.NewTarget(ctx, "dynamodb_index_read_target", &appautoscaling.TargetArgs{
			MaxCapacity:       pulumi.Int(100),
			MinCapacity:       pulumi.Int(5),
			ResourceId:        pulumi.Sprintf("table/%v/index/%v", example.Name, indexName),
			ScalableDimension: pulumi.String("dynamodb:index:ReadCapacityUnits"),
			ServiceNamespace:  pulumi.String("dynamodb"),
		})
		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 dynamodbIndexReadTarget = new Aws.AppAutoScaling.Target("dynamodb_index_read_target", new()
    {
        MaxCapacity = 100,
        MinCapacity = 5,
        ResourceId = $"table/{example.Name}/index/{indexName}",
        ScalableDimension = "dynamodb:index:ReadCapacityUnits",
        ServiceNamespace = "dynamodb",
    });

});
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 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 dynamodbIndexReadTarget = new Target("dynamodbIndexReadTarget", TargetArgs.builder()
            .maxCapacity(100)
            .minCapacity(5)
            .resourceId(String.format("table/%s/index/%s", example.name(),indexName))
            .scalableDimension("dynamodb:index:ReadCapacityUnits")
            .serviceNamespace("dynamodb")
            .build());

    }
}
resources:
  dynamodbIndexReadTarget:
    type: aws:appautoscaling:Target
    name: dynamodb_index_read_target
    properties:
      maxCapacity: 100
      minCapacity: 5
      resourceId: table/${example.name}/index/${indexName}
      scalableDimension: dynamodb:index:ReadCapacityUnits
      serviceNamespace: dynamodb

The resourceId format extends to table/{table-name}/index/{index-name} for GSI scaling. The scalableDimension changes to dynamodb:index:ReadCapacityUnits to target the index specifically. This lets you scale indexes independently from their base table.

Scale ECS service task count

Container workloads running in ECS need to scale task count up or down based on metrics like CPU utilization or request volume.

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/${example.name}/${exampleAwsEcsService.name}`,
    scalableDimension: "ecs:service:DesiredCount",
    serviceNamespace: "ecs",
});
import pulumi
import pulumi_aws as aws

ecs_target = aws.appautoscaling.Target("ecs_target",
    max_capacity=4,
    min_capacity=1,
    resource_id=f"service/{example['name']}/{example_aws_ecs_service['name']}",
    scalable_dimension="ecs:service:DesiredCount",
    service_namespace="ecs")
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 {
		_, err := appautoscaling.NewTarget(ctx, "ecs_target", &appautoscaling.TargetArgs{
			MaxCapacity:       pulumi.Int(4),
			MinCapacity:       pulumi.Int(1),
			ResourceId:        pulumi.Sprintf("service/%v/%v", example.Name, exampleAwsEcsService.Name),
			ScalableDimension: pulumi.String("ecs:service:DesiredCount"),
			ServiceNamespace:  pulumi.String("ecs"),
		})
		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/{example.Name}/{exampleAwsEcsService.Name}",
        ScalableDimension = "ecs:service:DesiredCount",
        ServiceNamespace = "ecs",
    });

});
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 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(String.format("service/%s/%s", example.name(),exampleAwsEcsService.name()))
            .scalableDimension("ecs:service:DesiredCount")
            .serviceNamespace("ecs")
            .build());

    }
}
resources:
  ecsTarget:
    type: aws:appautoscaling:Target
    name: ecs_target
    properties:
      maxCapacity: 4
      minCapacity: 1
      resourceId: service/${example.name}/${exampleAwsEcsService.name}
      scalableDimension: ecs:service:DesiredCount
      serviceNamespace: ecs

The resourceId format for ECS is service/{cluster-name}/{service-name}. The scalableDimension ecs:service:DesiredCount tells Application AutoScaling to adjust the number of running tasks. When policies trigger, Application AutoScaling updates the service’s desired count within the min/max bounds.

Scale Aurora read replica count

Aurora clusters serving read-heavy workloads can add or remove read replicas automatically to handle traffic spikes.

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,
});
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)
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 {
		_, 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
		}
		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,
    });

});
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 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());

    }
}
resources:
  replicas:
    type: aws:appautoscaling:Target
    properties:
      serviceNamespace: rds
      scalableDimension: rds:cluster:ReadReplicaCount
      resourceId: cluster:${example.id}
      minCapacity: 1
      maxCapacity: 15

The resourceId format for Aurora is cluster:{cluster-id}. The scalableDimension rds:cluster:ReadReplicaCount controls how many read replicas exist. Application AutoScaling adds or removes replicas based on metrics like CPU or connection count.

Scale MSK broker storage volume size

Kafka clusters on MSK can automatically expand broker storage as data volume grows, avoiding manual intervention when partitions fill up.

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

const mskTarget = new aws.appautoscaling.Target("msk_target", {
    serviceNamespace: "kafka",
    scalableDimension: "kafka:broker-storage:VolumeSize",
    resourceId: example.arn,
    minCapacity: 1,
    maxCapacity: 8,
});
import pulumi
import pulumi_aws as aws

msk_target = aws.appautoscaling.Target("msk_target",
    service_namespace="kafka",
    scalable_dimension="kafka:broker-storage:VolumeSize",
    resource_id=example["arn"],
    min_capacity=1,
    max_capacity=8)
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.NewTarget(ctx, "msk_target", &appautoscaling.TargetArgs{
			ServiceNamespace:  pulumi.String("kafka"),
			ScalableDimension: pulumi.String("kafka:broker-storage:VolumeSize"),
			ResourceId:        pulumi.Any(example.Arn),
			MinCapacity:       pulumi.Int(1),
			MaxCapacity:       pulumi.Int(8),
		})
		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 mskTarget = new Aws.AppAutoScaling.Target("msk_target", new()
    {
        ServiceNamespace = "kafka",
        ScalableDimension = "kafka:broker-storage:VolumeSize",
        ResourceId = example.Arn,
        MinCapacity = 1,
        MaxCapacity = 8,
    });

});
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 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 mskTarget = new Target("mskTarget", TargetArgs.builder()
            .serviceNamespace("kafka")
            .scalableDimension("kafka:broker-storage:VolumeSize")
            .resourceId(example.arn())
            .minCapacity(1)
            .maxCapacity(8)
            .build());

    }
}
resources:
  mskTarget:
    type: aws:appautoscaling:Target
    name: msk_target
    properties:
      serviceNamespace: kafka
      scalableDimension: kafka:broker-storage:VolumeSize
      resourceId: ${example.arn}
      minCapacity: 1
      maxCapacity: 8

The resourceId for MSK uses the cluster ARN directly. The scalableDimension kafka:broker-storage:VolumeSize targets storage capacity. Application AutoScaling expands volumes when utilization thresholds are crossed, but cannot shrink them (storage expansion is one-way).

Beyond these examples

These snippets focus on specific scalable target features: DynamoDB table and index capacity scaling, ECS task count and Aurora replica scaling, and MSK broker storage scaling. They’re intentionally minimal rather than full autoscaling solutions.

The examples reference pre-existing infrastructure such as DynamoDB tables and indexes, ECS clusters and services, Aurora clusters, and MSK clusters. They focus on registering scalable targets rather than provisioning the underlying resources or defining scaling policies.

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

  • IAM role configuration (roleArn, usually auto-managed)
  • Suspended state controls (suspendedState)
  • Tagging and lifecycle management
  • Scaling policies (configured via aws.appautoscaling.Policy)

These omissions are intentional: the goal is to illustrate how each scalable target is wired, not provide drop-in autoscaling modules. See the Application AutoScaling Target resource reference for all available configuration options.

Let's configure AWS Application Auto Scaling Targets

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

Try Pulumi Cloud for FREE

Frequently Asked Questions

Tagging & Resource Lifecycle
Why am I seeing perpetual diffs with tags on my scalable target?
Scalable targets created before 2023-03-20 may not have an assigned arn, preventing them from using tags or participating in defaultTags. Use lifecycle.ignore_changes to suppress tagsAll differences for these older resources.
What properties can't I change after creation?
Three properties are immutable: resourceId, scalableDimension, and serviceNamespace. Changing any of these requires replacing the resource.
IAM & Permissions
Why is my custom IAM role being ignored?
Application Auto Scaling automatically manages IAM Service-Linked Roles for most service namespaces, and custom IAM Roles specified in roleArn are ignored by the API. The service handles role creation when you register certain namespaces for the first time.
Resource Configuration
What's the correct resourceId format for my service?

The format varies by service:

  • DynamoDB tables: table/${name}
  • DynamoDB indexes: table/${name}/index/${indexName}
  • ECS services: service/${cluster}/${service}
  • RDS clusters: cluster:${id}
  • MSK/Kafka: Use the cluster ARN directly
How do I temporarily pause autoscaling for a target?
Configure the suspendedState property to suspend scaling activities for the scalable target.
What are the capacity limits I need to configure?
You must specify both minCapacity and maxCapacity for every scalable target. These define the range within which Application Auto Scaling can adjust your resource’s capacity.

Using a different cloud?

Explore compute guides for other cloud providers: