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 with cron expressions, and parameter passing with execution 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.

Apply a document to a single instance

Most SSM workflows start by applying a document to a specific instance, such as installing software or running a configuration script.

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 run. The targets array defines where to run it: set key to “InstanceIds” and values to an array of instance IDs. This creates a one-to-one association between the document and the instance.

Apply a document to all managed instances

Fleet-wide operations like installing monitoring agents or applying security patches often target every managed instance in an account.

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. This example uses an AWS-managed document (AmazonCloudWatch-ManageAgent) rather than a custom one, showing how to reference documents from AWS’s catalog.

Target instances by tag key and value

Tag-based targeting lets you apply documents to logical groups like all development servers or all instances in a specific application tier.

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 property uses “tag:Environment” syntax to filter by tag. The values array specifies which tag values to match. This runs the document on all instances tagged with Environment=Development, automatically including new instances as they’re tagged.

Schedule recurring document execution

Maintenance tasks like patching or log rotation often run on a schedule rather than immediately at association creation.

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. Without a schedule, associations run immediately and only once (unless the document itself loops).

Pass parameters and control execution behavior

Complex automation often requires passing shell commands or configuration values to the document, along with controls for concurrency 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 configuration to the document. For AWS-RunShellScript, this includes commands to execute, working directory, and timeout. The maxConcurrency and maxErrors properties control how many instances run simultaneously and how many failures are tolerated before stopping execution. The complianceSeverity property sets the severity level for compliance reporting.

Beyond these examples

These snippets focus on specific association-level features: instance and tag-based targeting, scheduling and parameter passing, and concurrency and error controls. They’re intentionally minimal rather than full automation workflows.

The examples 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 instances with SSM associations?

You have three targeting options:

  1. Specific instances - Set targets with key: "InstanceIds" and an array of instance IDs in values
  2. All managed instances - Use key: "InstanceIds" with values: ["*"]
  3. Tag-based targeting - Use key: "tag:TagName" with desired tag values in values (e.g., key: "tag:Environment", values: ["Development"])
What's the limit on targets for an association?
AWS supports a maximum of 5 targets per association. If you need to target more than 5 distinct target blocks, split your associations.
Scheduling & Execution
How do I schedule when my association runs?
Use scheduleExpression with a cron or rate expression. For example, cron(0 2 ? * SUN *) runs every Sunday at 2 AM.
Why does my association run immediately after creation?
By default, associations run immediately when created or updated, then follow the schedule. Set applyOnlyAtCronInterval to true to prevent immediate execution (note: this isn’t supported for rate expressions).
Can I restrict associations to run only during maintenance windows?
Yes, use calendarNames to specify one or more Systems Manager Change Calendar names. The association will only run when the Change Calendar is open.
Error Handling & Concurrency
How do I control how many targets run the association simultaneously?
Use maxConcurrency to specify a number (e.g., "10") or percentage (e.g., "10%") of targets allowed to run at the same time.
How do I limit errors before the association stops running?
Set maxErrors to a number or percentage. For example, "3" stops after the fourth error, while "10%" for 50 associations stops after the sixth error.
How do I wait for an association to succeed during creation?
Use waitForSuccessTimeoutSeconds to specify how many seconds to wait for Success status. The create operation will fail if this timeout is reached without success.
Configuration & Lifecycle
What happens if I change the document name?
The name property is immutable, so changing it forces replacement of the entire association.
What's automationTargetParameterName used for?
This is required for associations using Automation documents with rate controls. Set it to the SSM document parameter that defines how your automation will branch out.

Using a different cloud?

Explore compute guides for other cloud providers: