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 separate DeliveryChannel resource to store configuration snapshots and use RecorderStatus to activate the recorder. 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. Without recordingGroup or recordingMode properties, the recorder uses AWS Config defaults.

Exclude specific resource types from recording

Large accounts often reduce 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 property controls which resources Config tracks. Setting allSupported to false disables the default “record everything” behavior. The exclusionByResourceTypes block lists resource types to skip, and recordingStrategies tells Config to use exclusion-based filtering. This configuration tracks all resources except EC2 instances.

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 property sets the default recording frequency for all resources. The recordingModeOverride block defines exceptions: here, EC2 network interfaces are recorded daily while other resources (EC2 instances) follow the continuous default. The resourceTypes list in recordingGroup specifies which resources to track when allSupported is false.

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 full 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 complete Config pipeline.

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

  • Delivery channel configuration (separate resource)
  • Recorder activation (requires RecorderStatus resource)
  • Global resource recording (includeGlobalResourceTypes)
  • SNS notifications for configuration changes

These omissions are intentional: the goal is to illustrate how each recorder feature is wired, not provide drop-in compliance 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

Getting Started & Common Issues
Why doesn't my Config recorder start automatically after creation?
The recorder resource does not start automatically. You must use aws.cfg.RecorderStatus to start the recorder after creating it.
How do I start my Config recorder?
Starting the recorder requires both a delivery channel and the recorder itself. Create the recorder and delivery channel first, then use aws.cfg.RecorderStatus to start recording. This separate resource exists because of the circular dependency between recorder and delivery channel.
What happens if I change the recorder's name?
The name property is immutable. Changing it will destroy and recreate the recorder resource.
Configuration & Recording
How do I exclude specific resource types from recording?
Configure recordingGroup with exclusionByResourceTypes containing the resource types to exclude (e.g., AWS::EC2::Instance), and set recordingStrategies.useOnly to EXCLUSION_BY_RESOURCE_TYPES.
Can I use different recording frequencies for different resource types?
Yes, use recordingModeOverride within recordingMode to specify different frequencies per resource type. For example, you can record most resources continuously while recording network interfaces daily.
What recording frequencies are available?
You can set recordingFrequency to CONTINUOUS (default) or DAILY. Use recordingModeOverride to apply different frequencies to specific resource types.
IAM & Permissions
What IAM role does the Config recorder need?
The recorder requires an IAM role with permissions to make read/write requests to the delivery channel and describe AWS resources. The role’s trust policy must allow the config.amazonaws.com service principal to assume it.
What's the default name for a Config recorder?
The recorder name defaults to default if not specified.

Using a different cloud?

Explore monitoring guides for other cloud providers: