Configure AWS Systems Manager Associations

The aws:ssm/association:Association resource, part of the Pulumi AWS provider, links SSM documents to EC2 instances or tag groups, defining what runs, where it runs, and when it runs. This guide focuses on three capabilities: instance and tag-based targeting, scheduled execution, and shell script parameters with error controls.

Associations depend on SSM documents (AWS-managed or custom) and EC2 instances with the SSM agent installed and proper IAM permissions. The examples are intentionally small. Combine them with your own documents, instances, and scheduling requirements.

Target a single instance by ID

Most associations start by targeting a specific instance, applying a document to one managed instance.

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

const example = new aws.ssm.Association("example", {
    name: exampleAwsSsmDocument.name,
    targets: [{
        key: "InstanceIds",
        values: [exampleAwsInstance.id],
    }],
});
import pulumi
import pulumi_aws as aws

example = aws.ssm.Association("example",
    name=example_aws_ssm_document["name"],
    targets=[{
        "key": "InstanceIds",
        "values": [example_aws_instance["id"]],
    }])
package main

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

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := ssm.NewAssociation(ctx, "example", &ssm.AssociationArgs{
			Name: pulumi.Any(exampleAwsSsmDocument.Name),
			Targets: ssm.AssociationTargetArray{
				&ssm.AssociationTargetArgs{
					Key: pulumi.String("InstanceIds"),
					Values: pulumi.StringArray{
						exampleAwsInstance.Id,
					},
				},
			},
		})
		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.Ssm.Association("example", new()
    {
        Name = exampleAwsSsmDocument.Name,
        Targets = new[]
        {
            new Aws.Ssm.Inputs.AssociationTargetArgs
            {
                Key = "InstanceIds",
                Values = new[]
                {
                    exampleAwsInstance.Id,
                },
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.ssm.Association;
import com.pulumi.aws.ssm.AssociationArgs;
import com.pulumi.aws.ssm.inputs.AssociationTargetArgs;
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 Association("example", AssociationArgs.builder()
            .name(exampleAwsSsmDocument.name())
            .targets(AssociationTargetArgs.builder()
                .key("InstanceIds")
                .values(exampleAwsInstance.id())
                .build())
            .build());

    }
}
resources:
  example:
    type: aws:ssm:Association
    properties:
      name: ${exampleAwsSsmDocument.name}
      targets:
        - key: InstanceIds
          values:
            - ${exampleAwsInstance.id}

The name property specifies which SSM document to apply. The targets array defines where to apply it: set key to “InstanceIds” and values to an array of instance IDs. This creates a one-to-one association between document and instance.

Target all managed instances in an account

Operations teams often need to apply configuration or monitoring across every managed instance without maintaining individual lists.

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

const example = new aws.ssm.Association("example", {
    name: "AmazonCloudWatch-ManageAgent",
    targets: [{
        key: "InstanceIds",
        values: ["*"],
    }],
});
import pulumi
import pulumi_aws as aws

example = aws.ssm.Association("example",
    name="AmazonCloudWatch-ManageAgent",
    targets=[{
        "key": "InstanceIds",
        "values": ["*"],
    }])
package main

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

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := ssm.NewAssociation(ctx, "example", &ssm.AssociationArgs{
			Name: pulumi.String("AmazonCloudWatch-ManageAgent"),
			Targets: ssm.AssociationTargetArray{
				&ssm.AssociationTargetArgs{
					Key: pulumi.String("InstanceIds"),
					Values: pulumi.StringArray{
						pulumi.String("*"),
					},
				},
			},
		})
		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.Ssm.Association("example", new()
    {
        Name = "AmazonCloudWatch-ManageAgent",
        Targets = new[]
        {
            new Aws.Ssm.Inputs.AssociationTargetArgs
            {
                Key = "InstanceIds",
                Values = new[]
                {
                    "*",
                },
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.ssm.Association;
import com.pulumi.aws.ssm.AssociationArgs;
import com.pulumi.aws.ssm.inputs.AssociationTargetArgs;
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 Association("example", AssociationArgs.builder()
            .name("AmazonCloudWatch-ManageAgent")
            .targets(AssociationTargetArgs.builder()
                .key("InstanceIds")
                .values("*")
                .build())
            .build());

    }
}
resources:
  example:
    type: aws:ssm:Association
    properties:
      name: AmazonCloudWatch-ManageAgent
      targets:
        - key: InstanceIds
          values:
            - '*'

Setting values to ["*"] with key “InstanceIds” targets all managed instances in your account. This example uses “AmazonCloudWatch-ManageAgent”, an AWS-managed document that configures CloudWatch monitoring. The wildcard approach scales automatically as you add or remove instances.

Target instances by tag key and value

Tag-based targeting applies documents to groups of instances that share common characteristics.

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

const example = new aws.ssm.Association("example", {
    name: "AmazonCloudWatch-ManageAgent",
    targets: [{
        key: "tag:Environment",
        values: ["Development"],
    }],
});
import pulumi
import pulumi_aws as aws

example = aws.ssm.Association("example",
    name="AmazonCloudWatch-ManageAgent",
    targets=[{
        "key": "tag:Environment",
        "values": ["Development"],
    }])
package main

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

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := ssm.NewAssociation(ctx, "example", &ssm.AssociationArgs{
			Name: pulumi.String("AmazonCloudWatch-ManageAgent"),
			Targets: ssm.AssociationTargetArray{
				&ssm.AssociationTargetArgs{
					Key: pulumi.String("tag:Environment"),
					Values: pulumi.StringArray{
						pulumi.String("Development"),
					},
				},
			},
		})
		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.Ssm.Association("example", new()
    {
        Name = "AmazonCloudWatch-ManageAgent",
        Targets = new[]
        {
            new Aws.Ssm.Inputs.AssociationTargetArgs
            {
                Key = "tag:Environment",
                Values = new[]
                {
                    "Development",
                },
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.ssm.Association;
import com.pulumi.aws.ssm.AssociationArgs;
import com.pulumi.aws.ssm.inputs.AssociationTargetArgs;
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 Association("example", AssociationArgs.builder()
            .name("AmazonCloudWatch-ManageAgent")
            .targets(AssociationTargetArgs.builder()
                .key("tag:Environment")
                .values("Development")
                .build())
            .build());

    }
}
resources:
  example:
    type: aws:ssm:Association
    properties:
      name: AmazonCloudWatch-ManageAgent
      targets:
        - key: tag:Environment
          values:
            - Development

The key format “tag:Environment” tells SSM to match instances by tag. The values array specifies which tag values to include. This association runs on all instances tagged with Environment=Development, making it easy to manage environment-specific configurations.

Schedule associations with cron expressions

Maintenance tasks like patching or updates often run on a schedule rather than immediately.

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

const example = new aws.ssm.Association("example", {
    name: exampleAwsSsmDocument.name,
    scheduleExpression: "cron(0 2 ? * SUN *)",
    targets: [{
        key: "InstanceIds",
        values: [exampleAwsInstance.id],
    }],
});
import pulumi
import pulumi_aws as aws

example = aws.ssm.Association("example",
    name=example_aws_ssm_document["name"],
    schedule_expression="cron(0 2 ? * SUN *)",
    targets=[{
        "key": "InstanceIds",
        "values": [example_aws_instance["id"]],
    }])
package main

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

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := ssm.NewAssociation(ctx, "example", &ssm.AssociationArgs{
			Name:               pulumi.Any(exampleAwsSsmDocument.Name),
			ScheduleExpression: pulumi.String("cron(0 2 ? * SUN *)"),
			Targets: ssm.AssociationTargetArray{
				&ssm.AssociationTargetArgs{
					Key: pulumi.String("InstanceIds"),
					Values: pulumi.StringArray{
						exampleAwsInstance.Id,
					},
				},
			},
		})
		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.Ssm.Association("example", new()
    {
        Name = exampleAwsSsmDocument.Name,
        ScheduleExpression = "cron(0 2 ? * SUN *)",
        Targets = new[]
        {
            new Aws.Ssm.Inputs.AssociationTargetArgs
            {
                Key = "InstanceIds",
                Values = new[]
                {
                    exampleAwsInstance.Id,
                },
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.ssm.Association;
import com.pulumi.aws.ssm.AssociationArgs;
import com.pulumi.aws.ssm.inputs.AssociationTargetArgs;
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 Association("example", AssociationArgs.builder()
            .name(exampleAwsSsmDocument.name())
            .scheduleExpression("cron(0 2 ? * SUN *)")
            .targets(AssociationTargetArgs.builder()
                .key("InstanceIds")
                .values(exampleAwsInstance.id())
                .build())
            .build());

    }
}
resources:
  example:
    type: aws:ssm:Association
    properties:
      name: ${exampleAwsSsmDocument.name}
      scheduleExpression: cron(0 2 ? * SUN *)
      targets:
        - key: InstanceIds
          values:
            - ${exampleAwsInstance.id}

The scheduleExpression property accepts cron or rate expressions. This example runs every Sunday at 2 AM UTC. By default, associations also run immediately when created; set applyOnlyAtCronInterval to true to skip the initial run.

Run shell scripts across multiple instances

System maintenance often requires running the same commands across a fleet with control over execution timing and error handling.

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

// First EC2 instance
const webServer1 = new aws.ec2.Instance("web_server_1", {
    ami: amazonLinux.id,
    instanceType: aws.ec2.InstanceType.T3_Micro,
    subnetId: _public.id,
    vpcSecurityGroupIds: [ec2Sg.id],
    iamInstanceProfile: ec2SsmProfile.name,
    userData: `#!/bin/bash
yum update -y
yum install -y amazon-ssm-agent
systemctl enable amazon-ssm-agent
systemctl start amazon-ssm-agent
`,
});
// Second EC2 instance
const webServer2 = new aws.ec2.Instance("web_server_2", {
    ami: amazonLinux.id,
    instanceType: aws.ec2.InstanceType.T3_Micro,
    subnetId: _public.id,
    vpcSecurityGroupIds: [ec2Sg.id],
    iamInstanceProfile: ec2SsmProfile.name,
    userData: `#!/bin/bash
yum update -y
yum install -y amazon-ssm-agent
systemctl enable amazon-ssm-agent
systemctl start amazon-ssm-agent
`,
});
// Removed EC2 provisioning dependencies for brevity
const systemUpdate = new aws.ssm.Association("system_update", {
    name: "AWS-RunShellScript",
    targets: [{
        key: "InstanceIds",
        values: [
            webServer1.id,
            webServer2.id,
        ],
    }],
    scheduleExpression: "cron(0 2 ? * SUN *)",
    parameters: {
        commands: std.join({
            separator: "\n",
            input: [
                "#!/bin/bash",
                "echo 'Starting system update on $(hostname)'",
                "echo 'Instance ID: $(curl -s http://169.254.169.254/latest/meta-data/instance-id)'",
                "yum update -y",
                "echo 'System update completed successfully'",
                "systemctl status httpd",
                "df -h",
                "free -m",
            ],
        }).then(invoke => invoke.result),
        workingDirectory: "/tmp",
        executionTimeout: "3600",
    },
    associationName: "weekly-system-update",
    complianceSeverity: "MEDIUM",
    maxConcurrency: "1",
    maxErrors: "0",
    tags: {
        Name: "Weekly System Update",
        Environment: "demo",
        Purpose: "maintenance",
    },
});
import pulumi
import pulumi_aws as aws
import pulumi_std as std

# First EC2 instance
web_server1 = aws.ec2.Instance("web_server_1",
    ami=amazon_linux["id"],
    instance_type=aws.ec2.InstanceType.T3_MICRO,
    subnet_id=public["id"],
    vpc_security_group_ids=[ec2_sg["id"]],
    iam_instance_profile=ec2_ssm_profile["name"],
    user_data="""#!/bin/bash
yum update -y
yum install -y amazon-ssm-agent
systemctl enable amazon-ssm-agent
systemctl start amazon-ssm-agent
""")
# Second EC2 instance
web_server2 = aws.ec2.Instance("web_server_2",
    ami=amazon_linux["id"],
    instance_type=aws.ec2.InstanceType.T3_MICRO,
    subnet_id=public["id"],
    vpc_security_group_ids=[ec2_sg["id"]],
    iam_instance_profile=ec2_ssm_profile["name"],
    user_data="""#!/bin/bash
yum update -y
yum install -y amazon-ssm-agent
systemctl enable amazon-ssm-agent
systemctl start amazon-ssm-agent
""")
# Removed EC2 provisioning dependencies for brevity
system_update = aws.ssm.Association("system_update",
    name="AWS-RunShellScript",
    targets=[{
        "key": "InstanceIds",
        "values": [
            web_server1.id,
            web_server2.id,
        ],
    }],
    schedule_expression="cron(0 2 ? * SUN *)",
    parameters={
        "commands": std.join(separator="\n",
            input=[
                "#!/bin/bash",
                "echo 'Starting system update on $(hostname)'",
                "echo 'Instance ID: $(curl -s http://169.254.169.254/latest/meta-data/instance-id)'",
                "yum update -y",
                "echo 'System update completed successfully'",
                "systemctl status httpd",
                "df -h",
                "free -m",
            ]).result,
        "workingDirectory": "/tmp",
        "executionTimeout": "3600",
    },
    association_name="weekly-system-update",
    compliance_severity="MEDIUM",
    max_concurrency="1",
    max_errors="0",
    tags={
        "Name": "Weekly System Update",
        "Environment": "demo",
        "Purpose": "maintenance",
    })
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/ec2"
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/iam"
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/ssm"
	"github.com/pulumi/pulumi-std/sdk/go/std"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		// First EC2 instance
		webServer1, err := ec2.NewInstance(ctx, "web_server_1", &ec2.InstanceArgs{
			Ami:          pulumi.Any(amazonLinux.Id),
			InstanceType: pulumi.String(ec2.InstanceType_T3_Micro),
			SubnetId:     pulumi.Any(public.Id),
			VpcSecurityGroupIds: pulumi.StringArray{
				ec2Sg.Id,
			},
			IamInstanceProfile: pulumi.Any(ec2SsmProfile.Name),
			UserData: pulumi.String(`#!/bin/bash
yum update -y
yum install -y amazon-ssm-agent
systemctl enable amazon-ssm-agent
systemctl start amazon-ssm-agent
`),
		})
		if err != nil {
			return err
		}
		// Second EC2 instance
		webServer2, err := ec2.NewInstance(ctx, "web_server_2", &ec2.InstanceArgs{
			Ami:          pulumi.Any(amazonLinux.Id),
			InstanceType: pulumi.String(ec2.InstanceType_T3_Micro),
			SubnetId:     pulumi.Any(public.Id),
			VpcSecurityGroupIds: pulumi.StringArray{
				ec2Sg.Id,
			},
			IamInstanceProfile: pulumi.Any(ec2SsmProfile.Name),
			UserData: pulumi.String(`#!/bin/bash
yum update -y
yum install -y amazon-ssm-agent
systemctl enable amazon-ssm-agent
systemctl start amazon-ssm-agent
`),
		})
		if err != nil {
			return err
		}
		invokeJoin, err := std.Join(ctx, &std.JoinArgs{
			Separator: "\n",
			Input: []string{
				"#!/bin/bash",
				"echo 'Starting system update on $(hostname)'",
				"echo 'Instance ID: $(curl -s http://169.254.169.254/latest/meta-data/instance-id)'",
				"yum update -y",
				"echo 'System update completed successfully'",
				"systemctl status httpd",
				"df -h",
				"free -m",
			},
		}, nil)
		if err != nil {
			return err
		}
		// Removed EC2 provisioning dependencies for brevity
		_, err = ssm.NewAssociation(ctx, "system_update", &ssm.AssociationArgs{
			Name: pulumi.String("AWS-RunShellScript"),
			Targets: ssm.AssociationTargetArray{
				&ssm.AssociationTargetArgs{
					Key: pulumi.String("InstanceIds"),
					Values: pulumi.StringArray{
						webServer1.ID(),
						webServer2.ID(),
					},
				},
			},
			ScheduleExpression: pulumi.String("cron(0 2 ? * SUN *)"),
			Parameters: pulumi.StringMap{
				"commands":         pulumi.String(invokeJoin.Result),
				"workingDirectory": pulumi.String("/tmp"),
				"executionTimeout": pulumi.String("3600"),
			},
			AssociationName:    pulumi.String("weekly-system-update"),
			ComplianceSeverity: pulumi.String("MEDIUM"),
			MaxConcurrency:     pulumi.String("1"),
			MaxErrors:          pulumi.String("0"),
			Tags: pulumi.StringMap{
				"Name":        pulumi.String("Weekly System Update"),
				"Environment": pulumi.String("demo"),
				"Purpose":     pulumi.String("maintenance"),
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;
using Std = Pulumi.Std;

return await Deployment.RunAsync(() => 
{
    // First EC2 instance
    var webServer1 = new Aws.Ec2.Instance("web_server_1", new()
    {
        Ami = amazonLinux.Id,
        InstanceType = Aws.Ec2.InstanceType.T3_Micro,
        SubnetId = @public.Id,
        VpcSecurityGroupIds = new[]
        {
            ec2Sg.Id,
        },
        IamInstanceProfile = ec2SsmProfile.Name,
        UserData = @"#!/bin/bash
yum update -y
yum install -y amazon-ssm-agent
systemctl enable amazon-ssm-agent
systemctl start amazon-ssm-agent
",
    });

    // Second EC2 instance
    var webServer2 = new Aws.Ec2.Instance("web_server_2", new()
    {
        Ami = amazonLinux.Id,
        InstanceType = Aws.Ec2.InstanceType.T3_Micro,
        SubnetId = @public.Id,
        VpcSecurityGroupIds = new[]
        {
            ec2Sg.Id,
        },
        IamInstanceProfile = ec2SsmProfile.Name,
        UserData = @"#!/bin/bash
yum update -y
yum install -y amazon-ssm-agent
systemctl enable amazon-ssm-agent
systemctl start amazon-ssm-agent
",
    });

    // Removed EC2 provisioning dependencies for brevity
    var systemUpdate = new Aws.Ssm.Association("system_update", new()
    {
        Name = "AWS-RunShellScript",
        Targets = new[]
        {
            new Aws.Ssm.Inputs.AssociationTargetArgs
            {
                Key = "InstanceIds",
                Values = new[]
                {
                    webServer1.Id,
                    webServer2.Id,
                },
            },
        },
        ScheduleExpression = "cron(0 2 ? * SUN *)",
        Parameters = 
        {
            { "commands", Std.Join.Invoke(new()
            {
                Separator = @"
",
                Input = new[]
                {
                    "#!/bin/bash",
                    "echo 'Starting system update on $(hostname)'",
                    "echo 'Instance ID: $(curl -s http://169.254.169.254/latest/meta-data/instance-id)'",
                    "yum update -y",
                    "echo 'System update completed successfully'",
                    "systemctl status httpd",
                    "df -h",
                    "free -m",
                },
            }).Apply(invoke => invoke.Result) },
            { "workingDirectory", "/tmp" },
            { "executionTimeout", "3600" },
        },
        AssociationName = "weekly-system-update",
        ComplianceSeverity = "MEDIUM",
        MaxConcurrency = "1",
        MaxErrors = "0",
        Tags = 
        {
            { "Name", "Weekly System Update" },
            { "Environment", "demo" },
            { "Purpose", "maintenance" },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.ec2.Instance;
import com.pulumi.aws.ec2.InstanceArgs;
import com.pulumi.aws.ssm.Association;
import com.pulumi.aws.ssm.AssociationArgs;
import com.pulumi.aws.ssm.inputs.AssociationTargetArgs;
import com.pulumi.std.StdFunctions;
import com.pulumi.std.inputs.JoinArgs;
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) {
        // First EC2 instance
        var webServer1 = new Instance("webServer1", InstanceArgs.builder()
            .ami(amazonLinux.id())
            .instanceType("t3.micro")
            .subnetId(public_.id())
            .vpcSecurityGroupIds(ec2Sg.id())
            .iamInstanceProfile(ec2SsmProfile.name())
            .userData("""
#!/bin/bash
yum update -y
yum install -y amazon-ssm-agent
systemctl enable amazon-ssm-agent
systemctl start amazon-ssm-agent
            """)
            .build());

        // Second EC2 instance
        var webServer2 = new Instance("webServer2", InstanceArgs.builder()
            .ami(amazonLinux.id())
            .instanceType("t3.micro")
            .subnetId(public_.id())
            .vpcSecurityGroupIds(ec2Sg.id())
            .iamInstanceProfile(ec2SsmProfile.name())
            .userData("""
#!/bin/bash
yum update -y
yum install -y amazon-ssm-agent
systemctl enable amazon-ssm-agent
systemctl start amazon-ssm-agent
            """)
            .build());

        // Removed EC2 provisioning dependencies for brevity
        var systemUpdate = new Association("systemUpdate", AssociationArgs.builder()
            .name("AWS-RunShellScript")
            .targets(AssociationTargetArgs.builder()
                .key("InstanceIds")
                .values(                
                    webServer1.id(),
                    webServer2.id())
                .build())
            .scheduleExpression("cron(0 2 ? * SUN *)")
            .parameters(Map.ofEntries(
                Map.entry("commands", StdFunctions.join(JoinArgs.builder()
                    .separator("""

                    """)
                    .input(                    
                        "#!/bin/bash",
                        "echo 'Starting system update on $(hostname)'",
                        "echo 'Instance ID: $(curl -s http://169.254.169.254/latest/meta-data/instance-id)'",
                        "yum update -y",
                        "echo 'System update completed successfully'",
                        "systemctl status httpd",
                        "df -h",
                        "free -m")
                    .build()).result()),
                Map.entry("workingDirectory", "/tmp"),
                Map.entry("executionTimeout", "3600")
            ))
            .associationName("weekly-system-update")
            .complianceSeverity("MEDIUM")
            .maxConcurrency("1")
            .maxErrors("0")
            .tags(Map.ofEntries(
                Map.entry("Name", "Weekly System Update"),
                Map.entry("Environment", "demo"),
                Map.entry("Purpose", "maintenance")
            ))
            .build());

    }
}
resources:
  # Removed EC2 provisioning dependencies for brevity
  systemUpdate:
    type: aws:ssm:Association
    name: system_update
    properties:
      name: AWS-RunShellScript
      targets:
        - key: InstanceIds
          values:
            - ${webServer1.id}
            - ${webServer2.id}
      scheduleExpression: cron(0 2 ? * SUN *)
      parameters:
        commands:
          fn::invoke:
            function: std:join
            arguments:
              separator: |2+
              input:
                - '#!/bin/bash'
                - echo 'Starting system update on $(hostname)'
                - 'echo ''Instance ID: $(curl -s http://169.254.169.254/latest/meta-data/instance-id)'''
                - yum update -y
                - echo 'System update completed successfully'
                - systemctl status httpd
                - df -h
                - free -m
            return: result
        workingDirectory: /tmp
        executionTimeout: '3600'
      associationName: weekly-system-update
      complianceSeverity: MEDIUM
      maxConcurrency: '1'
      maxErrors: '0'
      tags:
        Name: Weekly System Update
        Environment: demo
        Purpose: maintenance
  # First EC2 instance
  webServer1:
    type: aws:ec2:Instance
    name: web_server_1
    properties:
      ami: ${amazonLinux.id}
      instanceType: t3.micro
      subnetId: ${public.id}
      vpcSecurityGroupIds:
        - ${ec2Sg.id}
      iamInstanceProfile: ${ec2SsmProfile.name}
      userData: |
        #!/bin/bash
        yum update -y
        yum install -y amazon-ssm-agent
        systemctl enable amazon-ssm-agent
        systemctl start amazon-ssm-agent        
  # Second EC2 instance
  webServer2:
    type: aws:ec2:Instance
    name: web_server_2
    properties:
      ami: ${amazonLinux.id}
      instanceType: t3.micro
      subnetId: ${public.id}
      vpcSecurityGroupIds:
        - ${ec2Sg.id}
      iamInstanceProfile: ${ec2SsmProfile.name}
      userData: |
        #!/bin/bash
        yum update -y
        yum install -y amazon-ssm-agent
        systemctl enable amazon-ssm-agent
        systemctl start amazon-ssm-agent        

The parameters property passes inputs to the document. For “AWS-RunShellScript”, the commands parameter accepts shell commands as a string. The maxConcurrency property limits how many instances run simultaneously (“1” means sequential execution). The maxErrors property stops execution after the specified number of failures. The complianceSeverity property categorizes the association for compliance reporting.

Target multiple tag values simultaneously

When managing different server types that need the same maintenance, you can target multiple tag values in one association.

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

// SSM Association for Webbased Servers
const databaseAssociation = new aws.ssm.Association("database_association", {
    name: systemUpdate.name,
    targets: [{
        key: "tag:Role",
        values: [
            "WebServer",
            "Database",
        ],
    }],
    parameters: {
        restartServices: "true",
    },
    scheduleExpression: "cron(0 3 ? * SUN *)",
});
// EC2 Instance 1 - Web Server with "ServerType" tag
const webServer = new aws.ec2.Instance("web_server", {
    ami: amazonLinux.id,
    instanceType: aws.ec2.InstanceType.T3_Micro,
    subnetId: _default.id,
    vpcSecurityGroupIds: [ec2Sg.id],
    iamInstanceProfile: ec2SsmProfile.name,
    userData: std.base64encode({
        input: `#!/bin/bash
yum update -y
yum install -y amazon-ssm-agent
systemctl enable amazon-ssm-agent
systemctl start amazon-ssm-agent
    
# Install Apache web server
yum install -y httpd
systemctl enable httpd
systemctl start httpd
echo \"<h1>Web Server - ${prefix}</h1>\" > /var/www/html/index.html
`,
    }).then(invoke => invoke.result),
    tags: {
        Name: `${prefix}-web-server`,
        ServerType: "WebServer",
        Role: "WebServer",
        Environment: environment,
        Owner: owner,
    },
});
// EC2 Instance 2 - Database Server with "Role" tag
const databaseServer = new aws.ec2.Instance("database_server", {
    ami: amazonLinux.id,
    instanceType: aws.ec2.InstanceType.T3_Micro,
    subnetId: _default.id,
    vpcSecurityGroupIds: [ec2Sg.id],
    iamInstanceProfile: ec2SsmProfile.name,
    userData: std.base64encode({
        input: `#!/bin/bash
yum update -y
yum install -y amazon-ssm-agent
systemctl enable amazon-ssm-agent
systemctl start amazon-ssm-agent
    
# Install MySQL
yum install -y mysql-server
systemctl enable mysqld
systemctl start mysqld
`,
    }).then(invoke => invoke.result),
    tags: {
        Name: `${prefix}-database-server`,
        Role: "Database",
        Environment: environment,
        Owner: owner,
    },
});
import pulumi
import pulumi_aws as aws
import pulumi_std as std

# SSM Association for Webbased Servers
database_association = aws.ssm.Association("database_association",
    name=system_update["name"],
    targets=[{
        "key": "tag:Role",
        "values": [
            "WebServer",
            "Database",
        ],
    }],
    parameters={
        "restartServices": "true",
    },
    schedule_expression="cron(0 3 ? * SUN *)")
# EC2 Instance 1 - Web Server with "ServerType" tag
web_server = aws.ec2.Instance("web_server",
    ami=amazon_linux["id"],
    instance_type=aws.ec2.InstanceType.T3_MICRO,
    subnet_id=default["id"],
    vpc_security_group_ids=[ec2_sg["id"]],
    iam_instance_profile=ec2_ssm_profile["name"],
    user_data=std.base64encode(input=f"""#!/bin/bash
yum update -y
yum install -y amazon-ssm-agent
systemctl enable amazon-ssm-agent
systemctl start amazon-ssm-agent
    
# Install Apache web server
yum install -y httpd
systemctl enable httpd
systemctl start httpd
echo \"<h1>Web Server - {prefix}</h1>\" > /var/www/html/index.html
""").result,
    tags={
        "Name": f"{prefix}-web-server",
        "ServerType": "WebServer",
        "Role": "WebServer",
        "Environment": environment,
        "Owner": owner,
    })
# EC2 Instance 2 - Database Server with "Role" tag
database_server = aws.ec2.Instance("database_server",
    ami=amazon_linux["id"],
    instance_type=aws.ec2.InstanceType.T3_MICRO,
    subnet_id=default["id"],
    vpc_security_group_ids=[ec2_sg["id"]],
    iam_instance_profile=ec2_ssm_profile["name"],
    user_data=std.base64encode(input="""#!/bin/bash
yum update -y
yum install -y amazon-ssm-agent
systemctl enable amazon-ssm-agent
systemctl start amazon-ssm-agent
    
# Install MySQL
yum install -y mysql-server
systemctl enable mysqld
systemctl start mysqld
""").result,
    tags={
        "Name": f"{prefix}-database-server",
        "Role": "Database",
        "Environment": environment,
        "Owner": owner,
    })
package main

import (
	"fmt"

	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/ec2"
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/iam"
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/ssm"
	"github.com/pulumi/pulumi-std/sdk/go/std"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		// SSM Association for Webbased Servers
		_, err := ssm.NewAssociation(ctx, "database_association", &ssm.AssociationArgs{
			Name: pulumi.Any(systemUpdate.Name),
			Targets: ssm.AssociationTargetArray{
				&ssm.AssociationTargetArgs{
					Key: pulumi.String("tag:Role"),
					Values: pulumi.StringArray{
						pulumi.String("WebServer"),
						pulumi.String("Database"),
					},
				},
			},
			Parameters: pulumi.StringMap{
				"restartServices": pulumi.String("true"),
			},
			ScheduleExpression: pulumi.String("cron(0 3 ? * SUN *)"),
		})
		if err != nil {
			return err
		}
		invokeBase64encode, err := std.Base64encode(ctx, &std.Base64encodeArgs{
			Input: fmt.Sprintf(`#!/bin/bash
yum update -y
yum install -y amazon-ssm-agent
systemctl enable amazon-ssm-agent
systemctl start amazon-ssm-agent
    
# Install Apache web server
yum install -y httpd
systemctl enable httpd
systemctl start httpd
echo \"<h1>Web Server - %v</h1>\" > /var/www/html/index.html
`, prefix),
		}, nil)
		if err != nil {
			return err
		}
		// EC2 Instance 1 - Web Server with "ServerType" tag
		_, err = ec2.NewInstance(ctx, "web_server", &ec2.InstanceArgs{
			Ami:          pulumi.Any(amazonLinux.Id),
			InstanceType: pulumi.String(ec2.InstanceType_T3_Micro),
			SubnetId:     pulumi.Any(_default.Id),
			VpcSecurityGroupIds: pulumi.StringArray{
				ec2Sg.Id,
			},
			IamInstanceProfile: pulumi.Any(ec2SsmProfile.Name),
			UserData:           pulumi.String(invokeBase64encode.Result),
			Tags: pulumi.StringMap{
				"Name":        pulumi.Sprintf("%v-web-server", prefix),
				"ServerType":  pulumi.String("WebServer"),
				"Role":        pulumi.String("WebServer"),
				"Environment": pulumi.Any(environment),
				"Owner":       pulumi.Any(owner),
			},
		})
		if err != nil {
			return err
		}
		invokeBase64encode1, err := std.Base64encode(ctx, &std.Base64encodeArgs{
			Input: `#!/bin/bash
yum update -y
yum install -y amazon-ssm-agent
systemctl enable amazon-ssm-agent
systemctl start amazon-ssm-agent
    
# Install MySQL
yum install -y mysql-server
systemctl enable mysqld
systemctl start mysqld
`,
		}, nil)
		if err != nil {
			return err
		}
		// EC2 Instance 2 - Database Server with "Role" tag
		_, err = ec2.NewInstance(ctx, "database_server", &ec2.InstanceArgs{
			Ami:          pulumi.Any(amazonLinux.Id),
			InstanceType: pulumi.String(ec2.InstanceType_T3_Micro),
			SubnetId:     pulumi.Any(_default.Id),
			VpcSecurityGroupIds: pulumi.StringArray{
				ec2Sg.Id,
			},
			IamInstanceProfile: pulumi.Any(ec2SsmProfile.Name),
			UserData:           pulumi.String(invokeBase64encode1.Result),
			Tags: pulumi.StringMap{
				"Name":        pulumi.Sprintf("%v-database-server", prefix),
				"Role":        pulumi.String("Database"),
				"Environment": pulumi.Any(environment),
				"Owner":       pulumi.Any(owner),
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;
using Std = Pulumi.Std;

return await Deployment.RunAsync(() => 
{
    // SSM Association for Webbased Servers
    var databaseAssociation = new Aws.Ssm.Association("database_association", new()
    {
        Name = systemUpdate.Name,
        Targets = new[]
        {
            new Aws.Ssm.Inputs.AssociationTargetArgs
            {
                Key = "tag:Role",
                Values = new[]
                {
                    "WebServer",
                    "Database",
                },
            },
        },
        Parameters = 
        {
            { "restartServices", "true" },
        },
        ScheduleExpression = "cron(0 3 ? * SUN *)",
    });

    // EC2 Instance 1 - Web Server with "ServerType" tag
    var webServer = new Aws.Ec2.Instance("web_server", new()
    {
        Ami = amazonLinux.Id,
        InstanceType = Aws.Ec2.InstanceType.T3_Micro,
        SubnetId = @default.Id,
        VpcSecurityGroupIds = new[]
        {
            ec2Sg.Id,
        },
        IamInstanceProfile = ec2SsmProfile.Name,
        UserData = Std.Base64encode.Invoke(new()
        {
            Input = @$"#!/bin/bash
yum update -y
yum install -y amazon-ssm-agent
systemctl enable amazon-ssm-agent
systemctl start amazon-ssm-agent
    
# Install Apache web server
yum install -y httpd
systemctl enable httpd
systemctl start httpd
echo \""<h1>Web Server - {prefix}</h1>\"" > /var/www/html/index.html
",
        }).Apply(invoke => invoke.Result),
        Tags = 
        {
            { "Name", $"{prefix}-web-server" },
            { "ServerType", "WebServer" },
            { "Role", "WebServer" },
            { "Environment", environment },
            { "Owner", owner },
        },
    });

    // EC2 Instance 2 - Database Server with "Role" tag
    var databaseServer = new Aws.Ec2.Instance("database_server", new()
    {
        Ami = amazonLinux.Id,
        InstanceType = Aws.Ec2.InstanceType.T3_Micro,
        SubnetId = @default.Id,
        VpcSecurityGroupIds = new[]
        {
            ec2Sg.Id,
        },
        IamInstanceProfile = ec2SsmProfile.Name,
        UserData = Std.Base64encode.Invoke(new()
        {
            Input = @"#!/bin/bash
yum update -y
yum install -y amazon-ssm-agent
systemctl enable amazon-ssm-agent
systemctl start amazon-ssm-agent
    
# Install MySQL
yum install -y mysql-server
systemctl enable mysqld
systemctl start mysqld
",
        }).Apply(invoke => invoke.Result),
        Tags = 
        {
            { "Name", $"{prefix}-database-server" },
            { "Role", "Database" },
            { "Environment", environment },
            { "Owner", owner },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.ssm.Association;
import com.pulumi.aws.ssm.AssociationArgs;
import com.pulumi.aws.ssm.inputs.AssociationTargetArgs;
import com.pulumi.aws.ec2.Instance;
import com.pulumi.aws.ec2.InstanceArgs;
import com.pulumi.std.StdFunctions;
import com.pulumi.std.inputs.Base64encodeArgs;
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) {
        // SSM Association for Webbased Servers
        var databaseAssociation = new Association("databaseAssociation", AssociationArgs.builder()
            .name(systemUpdate.name())
            .targets(AssociationTargetArgs.builder()
                .key("tag:Role")
                .values(                
                    "WebServer",
                    "Database")
                .build())
            .parameters(Map.of("restartServices", "true"))
            .scheduleExpression("cron(0 3 ? * SUN *)")
            .build());

        // EC2 Instance 1 - Web Server with "ServerType" tag
        var webServer = new Instance("webServer", InstanceArgs.builder()
            .ami(amazonLinux.id())
            .instanceType("t3.micro")
            .subnetId(default_.id())
            .vpcSecurityGroupIds(ec2Sg.id())
            .iamInstanceProfile(ec2SsmProfile.name())
            .userData(StdFunctions.base64encode(Base64encodeArgs.builder()
                .input("""
#!/bin/bash
yum update -y
yum install -y amazon-ssm-agent
systemctl enable amazon-ssm-agent
systemctl start amazon-ssm-agent
    
# Install Apache web server
yum install -y httpd
systemctl enable httpd
systemctl start httpd
echo \"<h1>Web Server - %s</h1>\" > /var/www/html/index.html
", prefix))
                .build()).result())
            .tags(Map.ofEntries(
                Map.entry("Name", String.format("%s-web-server", prefix)),
                Map.entry("ServerType", "WebServer"),
                Map.entry("Role", "WebServer"),
                Map.entry("Environment", environment),
                Map.entry("Owner", owner)
            ))
            .build());

        // EC2 Instance 2 - Database Server with "Role" tag
        var databaseServer = new Instance("databaseServer", InstanceArgs.builder()
            .ami(amazonLinux.id())
            .instanceType("t3.micro")
            .subnetId(default_.id())
            .vpcSecurityGroupIds(ec2Sg.id())
            .iamInstanceProfile(ec2SsmProfile.name())
            .userData(StdFunctions.base64encode(Base64encodeArgs.builder()
                .input("""
#!/bin/bash
yum update -y
yum install -y amazon-ssm-agent
systemctl enable amazon-ssm-agent
systemctl start amazon-ssm-agent
    
# Install MySQL
yum install -y mysql-server
systemctl enable mysqld
systemctl start mysqld
                """)
                .build()).result())
            .tags(Map.ofEntries(
                Map.entry("Name", String.format("%s-database-server", prefix)),
                Map.entry("Role", "Database"),
                Map.entry("Environment", environment),
                Map.entry("Owner", owner)
            ))
            .build());

    }
}
resources:
  # SSM Association for Webbased Servers
  databaseAssociation:
    type: aws:ssm:Association
    name: database_association
    properties:
      name: ${systemUpdate.name}
      targets:
        - key: tag:Role
          values:
            - WebServer
            - Database
      parameters:
        restartServices: 'true'
      scheduleExpression: cron(0 3 ? * SUN *)
  # EC2 Instance 1 - Web Server with "ServerType" tag
  webServer:
    type: aws:ec2:Instance
    name: web_server
    properties:
      ami: ${amazonLinux.id}
      instanceType: t3.micro
      subnetId: ${default.id}
      vpcSecurityGroupIds:
        - ${ec2Sg.id}
      iamInstanceProfile: ${ec2SsmProfile.name}
      userData:
        fn::invoke:
          function: std:base64encode
          arguments:
            input: "#!/bin/bash\nyum update -y\nyum install -y amazon-ssm-agent\nsystemctl enable amazon-ssm-agent\nsystemctl start amazon-ssm-agent\n    \n# Install Apache web server\nyum install -y httpd\nsystemctl enable httpd\nsystemctl start httpd\necho \\\"<h1>Web Server - ${prefix}</h1>\\\" > /var/www/html/index.html\n"
          return: result
      tags:
        Name: ${prefix}-web-server
        ServerType: WebServer
        Role: WebServer
        Environment: ${environment}
        Owner: ${owner}
  # EC2 Instance 2 - Database Server with "Role" tag
  databaseServer:
    type: aws:ec2:Instance
    name: database_server
    properties:
      ami: ${amazonLinux.id}
      instanceType: t3.micro
      subnetId: ${default.id}
      vpcSecurityGroupIds:
        - ${ec2Sg.id}
      iamInstanceProfile: ${ec2SsmProfile.name}
      userData:
        fn::invoke:
          function: std:base64encode
          arguments:
            input: "#!/bin/bash\nyum update -y\nyum install -y amazon-ssm-agent\nsystemctl enable amazon-ssm-agent\nsystemctl start amazon-ssm-agent\n    \n# Install MySQL\nyum install -y mysql-server\nsystemctl enable mysqld\nsystemctl start mysqld\n"
          return: result
      tags:
        Name: ${prefix}-database-server
        Role: Database
        Environment: ${environment}
        Owner: ${owner}

The values array can include multiple tag values. This association targets instances with Role=WebServer OR Role=Database, applying the same document to both groups. This reduces the number of associations you need to manage for similar maintenance tasks.

Beyond these examples

These snippets focus on specific association-level features: instance and tag-based targeting, scheduled execution with cron expressions, and shell script parameters and error controls. They’re intentionally minimal rather than full automation workflows.

The examples may reference pre-existing infrastructure such as SSM documents (AWS-managed or custom), EC2 instances with SSM agent installed, and IAM instance profiles with SSM permissions. They focus on configuring the association rather than provisioning the underlying infrastructure.

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

  • Output location for storing execution results (outputLocation)
  • Change Calendar integration (calendarNames)
  • Automation document targeting (automationTargetParameterName)
  • Immediate execution control (applyOnlyAtCronInterval)

These omissions are intentional: the goal is to illustrate how each association feature is wired, not provide drop-in automation modules. See the SSM Association resource reference for all available configuration options.

Let's configure AWS Systems Manager Associations

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

Try Pulumi Cloud for FREE

Frequently Asked Questions

Targeting & Selection
How do I target all managed instances in my AWS account?
Set the targets key to InstanceIds with values as ["*"]. This targets every managed instance in your account.
How do I target instances by tag?
Use key: "tag:YourTagName" in the targets block and specify the tag values in the values array. For example, key: "tag:Environment" with values: ["Development"] targets all instances with that tag.
How do I target multiple specific instances?
Set key to InstanceIds and provide an array of instance IDs in values, such as values: [instance1.id, instance2.id].
How many targets can I specify in an association?
AWS currently supports a maximum of 5 targets per association. If you need to target more groups, create separate associations.
Scheduling & Execution
How do I schedule when an association runs?
Use the scheduleExpression property with a cron or rate expression. For example, scheduleExpression: "cron(0 2 ? * SUN *)" runs the association at 2 AM every Sunday.
Can I prevent an association from running immediately after creation?
Yes, set applyOnlyAtCronInterval to true. By default, associations run immediately and then follow the schedule. Note that this parameter isn’t supported for rate expressions.
Error Handling & Concurrency
How do I control how many targets run the association simultaneously?
Use maxConcurrency to specify either a number (e.g., "10") or a percentage (e.g., "10%"). This limits how many targets execute the association at the same time.
How do I limit errors before the association stops running?
Set maxErrors to a number or percentage. For example, maxErrors: "3" stops execution when the fourth error occurs, while "10%" stops at the error threshold based on your target count.
Configuration & Requirements
What happens if I change the document name?
The name property is immutable, so changing it forces replacement of the entire association resource.
When do I need to set automationTargetParameterName?
This is required for associations using an Automation document that targets resources with rate controls. Set it to the SSM document parameter that defines how your automation branches out.
What happens if waitForSuccessTimeoutSeconds expires?
If the association doesn’t reach Success status within the specified timeout, the create operation fails.

Using a different cloud?

Explore compute guides for other cloud providers: