Configure AWS Config Recorders

The aws:cfg/recorder:Recorder resource, part of the Pulumi AWS provider, defines an AWS Config recorder that captures resource configuration changes across your account. This guide focuses on three capabilities: basic recorder setup, resource type filtering, and recording frequency control.

Config recorders require an IAM role with appropriate permissions and don’t start automatically. You must create a delivery channel and use the RecorderStatus resource to activate recording. The examples are intentionally small. Combine them with your own delivery channel and activation logic.

Create a recorder with minimal configuration

AWS Config tracks resource configuration changes across your account, capturing snapshots and sending them to a delivery channel.

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

const assumeRole = aws.iam.getPolicyDocument({
    statements: [{
        effect: "Allow",
        principals: [{
            type: "Service",
            identifiers: ["config.amazonaws.com"],
        }],
        actions: ["sts:AssumeRole"],
    }],
});
const r = new aws.iam.Role("r", {
    name: "awsconfig-example",
    assumeRolePolicy: assumeRole.then(assumeRole => assumeRole.json),
});
const foo = new aws.cfg.Recorder("foo", {
    name: "example",
    roleArn: r.arn,
});
import pulumi
import pulumi_aws as aws

assume_role = aws.iam.get_policy_document(statements=[{
    "effect": "Allow",
    "principals": [{
        "type": "Service",
        "identifiers": ["config.amazonaws.com"],
    }],
    "actions": ["sts:AssumeRole"],
}])
r = aws.iam.Role("r",
    name="awsconfig-example",
    assume_role_policy=assume_role.json)
foo = aws.cfg.Recorder("foo",
    name="example",
    role_arn=r.arn)
package main

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

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		assumeRole, err := iam.GetPolicyDocument(ctx, &iam.GetPolicyDocumentArgs{
			Statements: []iam.GetPolicyDocumentStatement{
				{
					Effect: pulumi.StringRef("Allow"),
					Principals: []iam.GetPolicyDocumentStatementPrincipal{
						{
							Type: "Service",
							Identifiers: []string{
								"config.amazonaws.com",
							},
						},
					},
					Actions: []string{
						"sts:AssumeRole",
					},
				},
			},
		}, nil)
		if err != nil {
			return err
		}
		r, err := iam.NewRole(ctx, "r", &iam.RoleArgs{
			Name:             pulumi.String("awsconfig-example"),
			AssumeRolePolicy: pulumi.String(assumeRole.Json),
		})
		if err != nil {
			return err
		}
		_, err = cfg.NewRecorder(ctx, "foo", &cfg.RecorderArgs{
			Name:    pulumi.String("example"),
			RoleArn: r.Arn,
		})
		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 assumeRole = Aws.Iam.GetPolicyDocument.Invoke(new()
    {
        Statements = new[]
        {
            new Aws.Iam.Inputs.GetPolicyDocumentStatementInputArgs
            {
                Effect = "Allow",
                Principals = new[]
                {
                    new Aws.Iam.Inputs.GetPolicyDocumentStatementPrincipalInputArgs
                    {
                        Type = "Service",
                        Identifiers = new[]
                        {
                            "config.amazonaws.com",
                        },
                    },
                },
                Actions = new[]
                {
                    "sts:AssumeRole",
                },
            },
        },
    });

    var r = new Aws.Iam.Role("r", new()
    {
        Name = "awsconfig-example",
        AssumeRolePolicy = assumeRole.Apply(getPolicyDocumentResult => getPolicyDocumentResult.Json),
    });

    var foo = new Aws.Cfg.Recorder("foo", new()
    {
        Name = "example",
        RoleArn = r.Arn,
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.iam.IamFunctions;
import com.pulumi.aws.iam.inputs.GetPolicyDocumentArgs;
import com.pulumi.aws.iam.Role;
import com.pulumi.aws.iam.RoleArgs;
import com.pulumi.aws.cfg.Recorder;
import com.pulumi.aws.cfg.RecorderArgs;
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) {
        final var assumeRole = IamFunctions.getPolicyDocument(GetPolicyDocumentArgs.builder()
            .statements(GetPolicyDocumentStatementArgs.builder()
                .effect("Allow")
                .principals(GetPolicyDocumentStatementPrincipalArgs.builder()
                    .type("Service")
                    .identifiers("config.amazonaws.com")
                    .build())
                .actions("sts:AssumeRole")
                .build())
            .build());

        var r = new Role("r", RoleArgs.builder()
            .name("awsconfig-example")
            .assumeRolePolicy(assumeRole.json())
            .build());

        var foo = new Recorder("foo", RecorderArgs.builder()
            .name("example")
            .roleArn(r.arn())
            .build());

    }
}
resources:
  foo:
    type: aws:cfg:Recorder
    properties:
      name: example
      roleArn: ${r.arn}
  r:
    type: aws:iam:Role
    properties:
      name: awsconfig-example
      assumeRolePolicy: ${assumeRole.json}
variables:
  assumeRole:
    fn::invoke:
      function: aws:iam:getPolicyDocument
      arguments:
        statements:
          - effect: Allow
            principals:
              - type: Service
                identifiers:
                  - config.amazonaws.com
            actions:
              - sts:AssumeRole

The roleArn grants Config permissions to read resource configurations and write to the delivery channel. The name defaults to “default” if omitted. This example creates the IAM role inline with the basic assume-role policy Config requires.

Exclude specific resource types from recording

Large accounts reduce Config costs by excluding high-churn resources from continuous tracking.

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

const foo = new aws.cfg.Recorder("foo", {
    name: "example",
    roleArn: r.arn,
    recordingGroup: {
        allSupported: false,
        exclusionByResourceTypes: [{
            resourceTypes: ["AWS::EC2::Instance"],
        }],
        recordingStrategies: [{
            useOnly: "EXCLUSION_BY_RESOURCE_TYPES",
        }],
    },
});
import pulumi
import pulumi_aws as aws

foo = aws.cfg.Recorder("foo",
    name="example",
    role_arn=r["arn"],
    recording_group={
        "all_supported": False,
        "exclusion_by_resource_types": [{
            "resource_types": ["AWS::EC2::Instance"],
        }],
        "recording_strategies": [{
            "use_only": "EXCLUSION_BY_RESOURCE_TYPES",
        }],
    })
package main

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

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := cfg.NewRecorder(ctx, "foo", &cfg.RecorderArgs{
			Name:    pulumi.String("example"),
			RoleArn: pulumi.Any(r.Arn),
			RecordingGroup: &cfg.RecorderRecordingGroupArgs{
				AllSupported: pulumi.Bool(false),
				ExclusionByResourceTypes: cfg.RecorderRecordingGroupExclusionByResourceTypeArray{
					&cfg.RecorderRecordingGroupExclusionByResourceTypeArgs{
						ResourceTypes: pulumi.StringArray{
							pulumi.String("AWS::EC2::Instance"),
						},
					},
				},
				RecordingStrategies: cfg.RecorderRecordingGroupRecordingStrategyArray{
					&cfg.RecorderRecordingGroupRecordingStrategyArgs{
						UseOnly: pulumi.String("EXCLUSION_BY_RESOURCE_TYPES"),
					},
				},
			},
		})
		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 foo = new Aws.Cfg.Recorder("foo", new()
    {
        Name = "example",
        RoleArn = r.Arn,
        RecordingGroup = new Aws.Cfg.Inputs.RecorderRecordingGroupArgs
        {
            AllSupported = false,
            ExclusionByResourceTypes = new[]
            {
                new Aws.Cfg.Inputs.RecorderRecordingGroupExclusionByResourceTypeArgs
                {
                    ResourceTypes = new[]
                    {
                        "AWS::EC2::Instance",
                    },
                },
            },
            RecordingStrategies = new[]
            {
                new Aws.Cfg.Inputs.RecorderRecordingGroupRecordingStrategyArgs
                {
                    UseOnly = "EXCLUSION_BY_RESOURCE_TYPES",
                },
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.cfg.Recorder;
import com.pulumi.aws.cfg.RecorderArgs;
import com.pulumi.aws.cfg.inputs.RecorderRecordingGroupArgs;
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 foo = new Recorder("foo", RecorderArgs.builder()
            .name("example")
            .roleArn(r.arn())
            .recordingGroup(RecorderRecordingGroupArgs.builder()
                .allSupported(false)
                .exclusionByResourceTypes(RecorderRecordingGroupExclusionByResourceTypeArgs.builder()
                    .resourceTypes("AWS::EC2::Instance")
                    .build())
                .recordingStrategies(RecorderRecordingGroupRecordingStrategyArgs.builder()
                    .useOnly("EXCLUSION_BY_RESOURCE_TYPES")
                    .build())
                .build())
            .build());

    }
}
resources:
  foo:
    type: aws:cfg:Recorder
    properties:
      name: example
      roleArn: ${r.arn}
      recordingGroup:
        allSupported: false
        exclusionByResourceTypes:
          - resourceTypes:
              - AWS::EC2::Instance
        recordingStrategies:
          - useOnly: EXCLUSION_BY_RESOURCE_TYPES

The recordingGroup controls which resources Config tracks. Setting allSupported to false lets you use exclusionByResourceTypes to filter out specific types like EC2 instances. The recordingStrategies property tells Config to use exclusion-based filtering rather than inclusion.

Mix continuous and periodic recording frequencies

Some resources need continuous monitoring while others can be checked daily to balance compliance with cost.

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

const foo = new aws.cfg.Recorder("foo", {
    name: "example",
    roleArn: r.arn,
    recordingGroup: {
        allSupported: false,
        includeGlobalResourceTypes: false,
        resourceTypes: [
            "AWS::EC2::Instance",
            "AWS::EC2::NetworkInterface",
        ],
    },
    recordingMode: {
        recordingFrequency: "CONTINUOUS",
        recordingModeOverride: {
            description: "Only record EC2 network interfaces daily",
            resourceTypes: ["AWS::EC2::NetworkInterface"],
            recordingFrequency: "DAILY",
        },
    },
});
import pulumi
import pulumi_aws as aws

foo = aws.cfg.Recorder("foo",
    name="example",
    role_arn=r["arn"],
    recording_group={
        "all_supported": False,
        "include_global_resource_types": False,
        "resource_types": [
            "AWS::EC2::Instance",
            "AWS::EC2::NetworkInterface",
        ],
    },
    recording_mode={
        "recording_frequency": "CONTINUOUS",
        "recording_mode_override": {
            "description": "Only record EC2 network interfaces daily",
            "resource_types": ["AWS::EC2::NetworkInterface"],
            "recording_frequency": "DAILY",
        },
    })
package main

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

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := cfg.NewRecorder(ctx, "foo", &cfg.RecorderArgs{
			Name:    pulumi.String("example"),
			RoleArn: pulumi.Any(r.Arn),
			RecordingGroup: &cfg.RecorderRecordingGroupArgs{
				AllSupported:               pulumi.Bool(false),
				IncludeGlobalResourceTypes: pulumi.Bool(false),
				ResourceTypes: pulumi.StringArray{
					pulumi.String("AWS::EC2::Instance"),
					pulumi.String("AWS::EC2::NetworkInterface"),
				},
			},
			RecordingMode: &cfg.RecorderRecordingModeArgs{
				RecordingFrequency: pulumi.String("CONTINUOUS"),
				RecordingModeOverride: &cfg.RecorderRecordingModeRecordingModeOverrideArgs{
					Description: pulumi.String("Only record EC2 network interfaces daily"),
					ResourceTypes: pulumi.StringArray{
						pulumi.String("AWS::EC2::NetworkInterface"),
					},
					RecordingFrequency: pulumi.String("DAILY"),
				},
			},
		})
		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 foo = new Aws.Cfg.Recorder("foo", new()
    {
        Name = "example",
        RoleArn = r.Arn,
        RecordingGroup = new Aws.Cfg.Inputs.RecorderRecordingGroupArgs
        {
            AllSupported = false,
            IncludeGlobalResourceTypes = false,
            ResourceTypes = new[]
            {
                "AWS::EC2::Instance",
                "AWS::EC2::NetworkInterface",
            },
        },
        RecordingMode = new Aws.Cfg.Inputs.RecorderRecordingModeArgs
        {
            RecordingFrequency = "CONTINUOUS",
            RecordingModeOverride = new Aws.Cfg.Inputs.RecorderRecordingModeRecordingModeOverrideArgs
            {
                Description = "Only record EC2 network interfaces daily",
                ResourceTypes = new[]
                {
                    "AWS::EC2::NetworkInterface",
                },
                RecordingFrequency = "DAILY",
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.cfg.Recorder;
import com.pulumi.aws.cfg.RecorderArgs;
import com.pulumi.aws.cfg.inputs.RecorderRecordingGroupArgs;
import com.pulumi.aws.cfg.inputs.RecorderRecordingModeArgs;
import com.pulumi.aws.cfg.inputs.RecorderRecordingModeRecordingModeOverrideArgs;
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 foo = new Recorder("foo", RecorderArgs.builder()
            .name("example")
            .roleArn(r.arn())
            .recordingGroup(RecorderRecordingGroupArgs.builder()
                .allSupported(false)
                .includeGlobalResourceTypes(false)
                .resourceTypes(                
                    "AWS::EC2::Instance",
                    "AWS::EC2::NetworkInterface")
                .build())
            .recordingMode(RecorderRecordingModeArgs.builder()
                .recordingFrequency("CONTINUOUS")
                .recordingModeOverride(RecorderRecordingModeRecordingModeOverrideArgs.builder()
                    .description("Only record EC2 network interfaces daily")
                    .resourceTypes("AWS::EC2::NetworkInterface")
                    .recordingFrequency("DAILY")
                    .build())
                .build())
            .build());

    }
}
resources:
  foo:
    type: aws:cfg:Recorder
    properties:
      name: example
      roleArn: ${r.arn}
      recordingGroup:
        allSupported: false
        includeGlobalResourceTypes: false
        resourceTypes:
          - AWS::EC2::Instance
          - AWS::EC2::NetworkInterface
      recordingMode:
        recordingFrequency: CONTINUOUS
        recordingModeOverride:
          description: Only record EC2 network interfaces daily
          resourceTypes:
            - AWS::EC2::NetworkInterface
          recordingFrequency: DAILY

The recordingMode sets the default frequency for all resources. The recordingModeOverride applies a different frequency to specific resource types. Here, EC2 instances are tracked continuously while network interfaces are recorded daily, reducing the volume of configuration snapshots.

Beyond these examples

These snippets focus on specific recorder-level features: resource type filtering and exclusion, and recording frequency control. They’re intentionally minimal rather than complete Config deployments.

The examples reference pre-existing infrastructure such as IAM roles with Config service permissions. They focus on configuring the recorder rather than provisioning the full Config pipeline.

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

  • Delivery channel configuration (separate resource)
  • Starting the recorder (requires RecorderStatus resource)
  • Global resource recording (includeGlobalResourceTypes)
  • Recording strategy combinations beyond exclusion

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

Let's configure AWS Config Recorders

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

Try Pulumi Cloud for FREE

Frequently Asked Questions

Setup & Activation
Why isn't my Config Recorder recording anything after creation?
The recorder resource does not start automatically. You must use aws.cfg.RecorderStatus to start the recorder after creating both the recorder and its delivery channel.
Why do I need a separate RecorderStatus resource?
Starting the recorder requires a delivery channel, but creating a delivery channel requires a recorder. This circular dependency is why aws.cfg.RecorderStatus exists as a separate resource to start recording after both are created.
What's the correct order to set up AWS Config recording?
Create the recorder and delivery channel first, then use aws.cfg.RecorderStatus to start the recorder. The recorder won’t begin recording until explicitly started.
IAM & Permissions
What IAM permissions does the Config Recorder role need?
The role needs permissions to make read/write requests to the delivery channel and describe AWS resources. Use config.amazonaws.com as the service principal in the assume role policy.
Recording Configuration
Can I exclude specific resource types from recording?
Yes, configure exclusionByResourceTypes in recordingGroup with the resource types to exclude, and set useOnly to EXCLUSION_BY_RESOURCE_TYPES in recordingStrategies.
Can I record different resource types at different frequencies?
Yes, use recordingModeOverride within recordingMode to specify different frequencies (CONTINUOUS or DAILY) for specific resource types.
Resource Management
What happens if I change the recorder name?
The name property is immutable. Changing it will destroy and recreate the recorder, so choose the name carefully during initial creation.

Using a different cloud?

Explore monitoring guides for other cloud providers: