Configure AWS S3 Bucket Lifecycle Rules

The aws:s3/bucketLifecycleConfigurationV2:BucketLifecycleConfigurationV2 resource, part of the Pulumi AWS provider, defines lifecycle rules that transition objects between storage classes or delete them based on age, size, tags, or version status. This guide focuses on three capabilities: filter targeting by prefix, tags, and object size; storage class transitions and expiration; and version-aware lifecycle management.

Lifecycle configurations attach to existing S3 buckets. Rules that manage noncurrent versions require BucketVersioning to be enabled first. The examples are intentionally small. Combine them with your own bucket resources and transition schedules.

Apply lifecycle rules to objects by prefix

Most lifecycle configurations target specific object paths, such as logs or temporary data that should move to cheaper storage or expire.

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

const example = new aws.s3.BucketLifecycleConfiguration("example", {
    bucket: bucket.bucket,
    rules: [{
        id: "rule-1",
        filter: {
            prefix: "logs/",
        },
        status: "Enabled",
    }],
});
import pulumi
import pulumi_aws as aws

example = aws.s3.BucketLifecycleConfiguration("example",
    bucket=bucket["bucket"],
    rules=[{
        "id": "rule-1",
        "filter": {
            "prefix": "logs/",
        },
        "status": "Enabled",
    }])
package main

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

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := s3.NewBucketLifecycleConfiguration(ctx, "example", &s3.BucketLifecycleConfigurationArgs{
			Bucket: pulumi.Any(bucket.Bucket),
			Rules: s3.BucketLifecycleConfigurationRuleArray{
				&s3.BucketLifecycleConfigurationRuleArgs{
					Id: pulumi.String("rule-1"),
					Filter: &s3.BucketLifecycleConfigurationRuleFilterArgs{
						Prefix: pulumi.String("logs/"),
					},
					Status: pulumi.String("Enabled"),
				},
			},
		})
		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.S3.BucketLifecycleConfiguration("example", new()
    {
        Bucket = bucket.Bucket,
        Rules = new[]
        {
            new Aws.S3.Inputs.BucketLifecycleConfigurationRuleArgs
            {
                Id = "rule-1",
                Filter = new Aws.S3.Inputs.BucketLifecycleConfigurationRuleFilterArgs
                {
                    Prefix = "logs/",
                },
                Status = "Enabled",
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.s3.BucketLifecycleConfiguration;
import com.pulumi.aws.s3.BucketLifecycleConfigurationArgs;
import com.pulumi.aws.s3.inputs.BucketLifecycleConfigurationRuleArgs;
import com.pulumi.aws.s3.inputs.BucketLifecycleConfigurationRuleFilterArgs;
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 BucketLifecycleConfiguration("example", BucketLifecycleConfigurationArgs.builder()
            .bucket(bucket.bucket())
            .rules(BucketLifecycleConfigurationRuleArgs.builder()
                .id("rule-1")
                .filter(BucketLifecycleConfigurationRuleFilterArgs.builder()
                    .prefix("logs/")
                    .build())
                .status("Enabled")
                .build())
            .build());

    }
}
resources:
  example:
    type: aws:s3:BucketLifecycleConfiguration
    properties:
      bucket: ${bucket.bucket}
      rules:
        - id: rule-1
          filter:
            prefix: logs/
          status: Enabled

The filter property narrows which objects the rule affects. Setting prefix to “logs/” means the rule applies only to objects whose keys start with that path. The id and status properties identify and enable the rule. Without transition or expiration actions, this rule does nothing; add those to define what happens to matching objects.

Target objects by tag for lifecycle actions

Applications that tag objects can use those tags to control lifecycle behavior, applying different retention to production versus staging data.

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

const example = new aws.s3.BucketLifecycleConfiguration("example", {
    bucket: bucket.bucket,
    rules: [{
        id: "rule-1",
        filter: {
            tag: {
                key: "Name",
                value: "Staging",
            },
        },
        status: "Enabled",
    }],
});
import pulumi
import pulumi_aws as aws

example = aws.s3.BucketLifecycleConfiguration("example",
    bucket=bucket["bucket"],
    rules=[{
        "id": "rule-1",
        "filter": {
            "tag": {
                "key": "Name",
                "value": "Staging",
            },
        },
        "status": "Enabled",
    }])
package main

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

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := s3.NewBucketLifecycleConfiguration(ctx, "example", &s3.BucketLifecycleConfigurationArgs{
			Bucket: pulumi.Any(bucket.Bucket),
			Rules: s3.BucketLifecycleConfigurationRuleArray{
				&s3.BucketLifecycleConfigurationRuleArgs{
					Id: pulumi.String("rule-1"),
					Filter: &s3.BucketLifecycleConfigurationRuleFilterArgs{
						Tag: &s3.BucketLifecycleConfigurationRuleFilterTagArgs{
							Key:   pulumi.String("Name"),
							Value: pulumi.String("Staging"),
						},
					},
					Status: pulumi.String("Enabled"),
				},
			},
		})
		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.S3.BucketLifecycleConfiguration("example", new()
    {
        Bucket = bucket.Bucket,
        Rules = new[]
        {
            new Aws.S3.Inputs.BucketLifecycleConfigurationRuleArgs
            {
                Id = "rule-1",
                Filter = new Aws.S3.Inputs.BucketLifecycleConfigurationRuleFilterArgs
                {
                    Tag = new Aws.S3.Inputs.BucketLifecycleConfigurationRuleFilterTagArgs
                    {
                        Key = "Name",
                        Value = "Staging",
                    },
                },
                Status = "Enabled",
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.s3.BucketLifecycleConfiguration;
import com.pulumi.aws.s3.BucketLifecycleConfigurationArgs;
import com.pulumi.aws.s3.inputs.BucketLifecycleConfigurationRuleArgs;
import com.pulumi.aws.s3.inputs.BucketLifecycleConfigurationRuleFilterArgs;
import com.pulumi.aws.s3.inputs.BucketLifecycleConfigurationRuleFilterTagArgs;
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 BucketLifecycleConfiguration("example", BucketLifecycleConfigurationArgs.builder()
            .bucket(bucket.bucket())
            .rules(BucketLifecycleConfigurationRuleArgs.builder()
                .id("rule-1")
                .filter(BucketLifecycleConfigurationRuleFilterArgs.builder()
                    .tag(BucketLifecycleConfigurationRuleFilterTagArgs.builder()
                        .key("Name")
                        .value("Staging")
                        .build())
                    .build())
                .status("Enabled")
                .build())
            .build());

    }
}
resources:
  example:
    type: aws:s3:BucketLifecycleConfiguration
    properties:
      bucket: ${bucket.bucket}
      rules:
        - id: rule-1
          filter:
            tag:
              key: Name
              value: Staging
          status: Enabled

The filter.tag property matches objects with a specific key-value pair. Here, only objects tagged “Name=Staging” are affected. This lets you apply different lifecycle policies to objects in the same bucket based on classification metadata rather than path structure.

Combine prefix and tags for precise targeting

Complex environments often need rules that match both a path pattern and specific tags.

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

const example = new aws.s3.BucketLifecycleConfiguration("example", {
    bucket: bucket.bucket,
    rules: [{
        id: "rule-1",
        filter: {
            and: {
                prefix: "logs/",
                tags: {
                    Key1: "Value1",
                    Key2: "Value2",
                },
            },
        },
        status: "Enabled",
    }],
});
import pulumi
import pulumi_aws as aws

example = aws.s3.BucketLifecycleConfiguration("example",
    bucket=bucket["bucket"],
    rules=[{
        "id": "rule-1",
        "filter": {
            "and_": {
                "prefix": "logs/",
                "tags": {
                    "Key1": "Value1",
                    "Key2": "Value2",
                },
            },
        },
        "status": "Enabled",
    }])
package main

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

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := s3.NewBucketLifecycleConfiguration(ctx, "example", &s3.BucketLifecycleConfigurationArgs{
			Bucket: pulumi.Any(bucket.Bucket),
			Rules: s3.BucketLifecycleConfigurationRuleArray{
				&s3.BucketLifecycleConfigurationRuleArgs{
					Id: pulumi.String("rule-1"),
					Filter: &s3.BucketLifecycleConfigurationRuleFilterArgs{
						And: &s3.BucketLifecycleConfigurationRuleFilterAndArgs{
							Prefix: pulumi.String("logs/"),
							Tags: pulumi.StringMap{
								"Key1": pulumi.String("Value1"),
								"Key2": pulumi.String("Value2"),
							},
						},
					},
					Status: pulumi.String("Enabled"),
				},
			},
		})
		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.S3.BucketLifecycleConfiguration("example", new()
    {
        Bucket = bucket.Bucket,
        Rules = new[]
        {
            new Aws.S3.Inputs.BucketLifecycleConfigurationRuleArgs
            {
                Id = "rule-1",
                Filter = new Aws.S3.Inputs.BucketLifecycleConfigurationRuleFilterArgs
                {
                    And = new Aws.S3.Inputs.BucketLifecycleConfigurationRuleFilterAndArgs
                    {
                        Prefix = "logs/",
                        Tags = 
                        {
                            { "Key1", "Value1" },
                            { "Key2", "Value2" },
                        },
                    },
                },
                Status = "Enabled",
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.s3.BucketLifecycleConfiguration;
import com.pulumi.aws.s3.BucketLifecycleConfigurationArgs;
import com.pulumi.aws.s3.inputs.BucketLifecycleConfigurationRuleArgs;
import com.pulumi.aws.s3.inputs.BucketLifecycleConfigurationRuleFilterArgs;
import com.pulumi.aws.s3.inputs.BucketLifecycleConfigurationRuleFilterAndArgs;
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 BucketLifecycleConfiguration("example", BucketLifecycleConfigurationArgs.builder()
            .bucket(bucket.bucket())
            .rules(BucketLifecycleConfigurationRuleArgs.builder()
                .id("rule-1")
                .filter(BucketLifecycleConfigurationRuleFilterArgs.builder()
                    .and(BucketLifecycleConfigurationRuleFilterAndArgs.builder()
                        .prefix("logs/")
                        .tags(Map.ofEntries(
                            Map.entry("Key1", "Value1"),
                            Map.entry("Key2", "Value2")
                        ))
                        .build())
                    .build())
                .status("Enabled")
                .build())
            .build());

    }
}
resources:
  example:
    type: aws:s3:BucketLifecycleConfiguration
    properties:
      bucket: ${bucket.bucket}
      rules:
        - id: rule-1
          filter:
            and:
              prefix: logs/
              tags:
                Key1: Value1
                Key2: Value2
          status: Enabled

The and block combines multiple filter criteria. Both prefix and tags must match for the rule to apply. This prevents accidentally affecting objects that share a prefix but lack the required tags, or vice versa.

Control transitions for small objects

S3 applies a default 128 KB minimum for storage class transitions. To transition smaller objects, set an explicit size threshold.

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

const example = new aws.s3.BucketLifecycleConfiguration("example", {
    bucket: bucket.bucket,
    rules: [{
        id: "Allow small object transitions",
        filter: {
            objectSizeGreaterThan: 1,
        },
        status: "Enabled",
        transitions: [{
            days: 365,
            storageClass: "GLACIER_IR",
        }],
    }],
});
import pulumi
import pulumi_aws as aws

example = aws.s3.BucketLifecycleConfiguration("example",
    bucket=bucket["bucket"],
    rules=[{
        "id": "Allow small object transitions",
        "filter": {
            "object_size_greater_than": 1,
        },
        "status": "Enabled",
        "transitions": [{
            "days": 365,
            "storage_class": "GLACIER_IR",
        }],
    }])
package main

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

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := s3.NewBucketLifecycleConfiguration(ctx, "example", &s3.BucketLifecycleConfigurationArgs{
			Bucket: pulumi.Any(bucket.Bucket),
			Rules: s3.BucketLifecycleConfigurationRuleArray{
				&s3.BucketLifecycleConfigurationRuleArgs{
					Id: pulumi.String("Allow small object transitions"),
					Filter: &s3.BucketLifecycleConfigurationRuleFilterArgs{
						ObjectSizeGreaterThan: pulumi.Int(1),
					},
					Status: pulumi.String("Enabled"),
					Transitions: s3.BucketLifecycleConfigurationRuleTransitionArray{
						&s3.BucketLifecycleConfigurationRuleTransitionArgs{
							Days:         pulumi.Int(365),
							StorageClass: pulumi.String("GLACIER_IR"),
						},
					},
				},
			},
		})
		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.S3.BucketLifecycleConfiguration("example", new()
    {
        Bucket = bucket.Bucket,
        Rules = new[]
        {
            new Aws.S3.Inputs.BucketLifecycleConfigurationRuleArgs
            {
                Id = "Allow small object transitions",
                Filter = new Aws.S3.Inputs.BucketLifecycleConfigurationRuleFilterArgs
                {
                    ObjectSizeGreaterThan = 1,
                },
                Status = "Enabled",
                Transitions = new[]
                {
                    new Aws.S3.Inputs.BucketLifecycleConfigurationRuleTransitionArgs
                    {
                        Days = 365,
                        StorageClass = "GLACIER_IR",
                    },
                },
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.s3.BucketLifecycleConfiguration;
import com.pulumi.aws.s3.BucketLifecycleConfigurationArgs;
import com.pulumi.aws.s3.inputs.BucketLifecycleConfigurationRuleArgs;
import com.pulumi.aws.s3.inputs.BucketLifecycleConfigurationRuleFilterArgs;
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 BucketLifecycleConfiguration("example", BucketLifecycleConfigurationArgs.builder()
            .bucket(bucket.bucket())
            .rules(BucketLifecycleConfigurationRuleArgs.builder()
                .id("Allow small object transitions")
                .filter(BucketLifecycleConfigurationRuleFilterArgs.builder()
                    .objectSizeGreaterThan(1)
                    .build())
                .status("Enabled")
                .transitions(BucketLifecycleConfigurationRuleTransitionArgs.builder()
                    .days(365)
                    .storageClass("GLACIER_IR")
                    .build())
                .build())
            .build());

    }
}
resources:
  example:
    type: aws:s3:BucketLifecycleConfiguration
    properties:
      bucket: ${bucket.bucket}
      rules:
        - id: Allow small object transitions
          filter:
            objectSizeGreaterThan: 1
          status: Enabled
          transitions:
            - days: 365
              storageClass: GLACIER_IR

The objectSizeGreaterThan property overrides the default 128 KB minimum. Setting it to 1 byte allows any object to transition. The transitions array defines when objects move to cheaper storage classes; here, objects move to GLACIER_IR after 365 days. Without this size filter, objects under 128 KB would never transition regardless of age.

Manage current and noncurrent object versions

Versioned buckets accumulate old object versions. Lifecycle rules can transition or delete noncurrent versions while preserving recent ones.

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

const bucket = new aws.s3.Bucket("bucket", {bucket: "my-bucket"});
const bucketAcl = new aws.s3.BucketAcl("bucket_acl", {
    bucket: bucket.bucket,
    acl: "private",
});
const bucket_config = new aws.s3.BucketLifecycleConfiguration("bucket-config", {
    bucket: bucket.bucket,
    rules: [
        {
            id: "log",
            expiration: {
                days: 90,
            },
            filter: {
                and: {
                    prefix: "log/",
                    tags: {
                        rule: "log",
                        autoclean: "true",
                    },
                },
            },
            status: "Enabled",
            transitions: [
                {
                    days: 30,
                    storageClass: "STANDARD_IA",
                },
                {
                    days: 60,
                    storageClass: "GLACIER",
                },
            ],
        },
        {
            id: "tmp",
            filter: {
                prefix: "tmp/",
            },
            expiration: {
                date: "2023-01-13T00:00:00Z",
            },
            status: "Enabled",
        },
    ],
});
const versioningBucket = new aws.s3.Bucket("versioning_bucket", {bucket: "my-versioning-bucket"});
const versioningBucketAcl = new aws.s3.BucketAcl("versioning_bucket_acl", {
    bucket: versioningBucket.bucket,
    acl: "private",
});
const versioning = new aws.s3.BucketVersioning("versioning", {
    bucket: versioningBucket.bucket,
    versioningConfiguration: {
        status: "Enabled",
    },
});
const versioning_bucket_config = new aws.s3.BucketLifecycleConfiguration("versioning-bucket-config", {
    bucket: versioningBucket.bucket,
    rules: [{
        id: "config",
        filter: {
            prefix: "config/",
        },
        noncurrentVersionExpiration: {
            noncurrentDays: 90,
        },
        noncurrentVersionTransitions: [
            {
                noncurrentDays: 30,
                storageClass: "STANDARD_IA",
            },
            {
                noncurrentDays: 60,
                storageClass: "GLACIER",
            },
        ],
        status: "Enabled",
    }],
}, {
    dependsOn: [versioning],
});
import pulumi
import pulumi_aws as aws

bucket = aws.s3.Bucket("bucket", bucket="my-bucket")
bucket_acl = aws.s3.BucketAcl("bucket_acl",
    bucket=bucket.bucket,
    acl="private")
bucket_config = aws.s3.BucketLifecycleConfiguration("bucket-config",
    bucket=bucket.bucket,
    rules=[
        {
            "id": "log",
            "expiration": {
                "days": 90,
            },
            "filter": {
                "and_": {
                    "prefix": "log/",
                    "tags": {
                        "rule": "log",
                        "autoclean": "true",
                    },
                },
            },
            "status": "Enabled",
            "transitions": [
                {
                    "days": 30,
                    "storage_class": "STANDARD_IA",
                },
                {
                    "days": 60,
                    "storage_class": "GLACIER",
                },
            ],
        },
        {
            "id": "tmp",
            "filter": {
                "prefix": "tmp/",
            },
            "expiration": {
                "date": "2023-01-13T00:00:00Z",
            },
            "status": "Enabled",
        },
    ])
versioning_bucket = aws.s3.Bucket("versioning_bucket", bucket="my-versioning-bucket")
versioning_bucket_acl = aws.s3.BucketAcl("versioning_bucket_acl",
    bucket=versioning_bucket.bucket,
    acl="private")
versioning = aws.s3.BucketVersioning("versioning",
    bucket=versioning_bucket.bucket,
    versioning_configuration={
        "status": "Enabled",
    })
versioning_bucket_config = aws.s3.BucketLifecycleConfiguration("versioning-bucket-config",
    bucket=versioning_bucket.bucket,
    rules=[{
        "id": "config",
        "filter": {
            "prefix": "config/",
        },
        "noncurrent_version_expiration": {
            "noncurrent_days": 90,
        },
        "noncurrent_version_transitions": [
            {
                "noncurrent_days": 30,
                "storage_class": "STANDARD_IA",
            },
            {
                "noncurrent_days": 60,
                "storage_class": "GLACIER",
            },
        ],
        "status": "Enabled",
    }],
    opts = pulumi.ResourceOptions(depends_on=[versioning]))
package main

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

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		bucket, err := s3.NewBucket(ctx, "bucket", &s3.BucketArgs{
			Bucket: pulumi.String("my-bucket"),
		})
		if err != nil {
			return err
		}
		_, err = s3.NewBucketAcl(ctx, "bucket_acl", &s3.BucketAclArgs{
			Bucket: bucket.Bucket,
			Acl:    pulumi.String("private"),
		})
		if err != nil {
			return err
		}
		_, err = s3.NewBucketLifecycleConfiguration(ctx, "bucket-config", &s3.BucketLifecycleConfigurationArgs{
			Bucket: bucket.Bucket,
			Rules: s3.BucketLifecycleConfigurationRuleArray{
				&s3.BucketLifecycleConfigurationRuleArgs{
					Id: pulumi.String("log"),
					Expiration: &s3.BucketLifecycleConfigurationRuleExpirationArgs{
						Days: pulumi.Int(90),
					},
					Filter: &s3.BucketLifecycleConfigurationRuleFilterArgs{
						And: &s3.BucketLifecycleConfigurationRuleFilterAndArgs{
							Prefix: pulumi.String("log/"),
							Tags: pulumi.StringMap{
								"rule":      pulumi.String("log"),
								"autoclean": pulumi.String("true"),
							},
						},
					},
					Status: pulumi.String("Enabled"),
					Transitions: s3.BucketLifecycleConfigurationRuleTransitionArray{
						&s3.BucketLifecycleConfigurationRuleTransitionArgs{
							Days:         pulumi.Int(30),
							StorageClass: pulumi.String("STANDARD_IA"),
						},
						&s3.BucketLifecycleConfigurationRuleTransitionArgs{
							Days:         pulumi.Int(60),
							StorageClass: pulumi.String("GLACIER"),
						},
					},
				},
				&s3.BucketLifecycleConfigurationRuleArgs{
					Id: pulumi.String("tmp"),
					Filter: &s3.BucketLifecycleConfigurationRuleFilterArgs{
						Prefix: pulumi.String("tmp/"),
					},
					Expiration: &s3.BucketLifecycleConfigurationRuleExpirationArgs{
						Date: pulumi.String("2023-01-13T00:00:00Z"),
					},
					Status: pulumi.String("Enabled"),
				},
			},
		})
		if err != nil {
			return err
		}
		versioningBucket, err := s3.NewBucket(ctx, "versioning_bucket", &s3.BucketArgs{
			Bucket: pulumi.String("my-versioning-bucket"),
		})
		if err != nil {
			return err
		}
		_, err = s3.NewBucketAcl(ctx, "versioning_bucket_acl", &s3.BucketAclArgs{
			Bucket: versioningBucket.Bucket,
			Acl:    pulumi.String("private"),
		})
		if err != nil {
			return err
		}
		versioning, err := s3.NewBucketVersioning(ctx, "versioning", &s3.BucketVersioningArgs{
			Bucket: versioningBucket.Bucket,
			VersioningConfiguration: &s3.BucketVersioningVersioningConfigurationArgs{
				Status: pulumi.String("Enabled"),
			},
		})
		if err != nil {
			return err
		}
		_, err = s3.NewBucketLifecycleConfiguration(ctx, "versioning-bucket-config", &s3.BucketLifecycleConfigurationArgs{
			Bucket: versioningBucket.Bucket,
			Rules: s3.BucketLifecycleConfigurationRuleArray{
				&s3.BucketLifecycleConfigurationRuleArgs{
					Id: pulumi.String("config"),
					Filter: &s3.BucketLifecycleConfigurationRuleFilterArgs{
						Prefix: pulumi.String("config/"),
					},
					NoncurrentVersionExpiration: &s3.BucketLifecycleConfigurationRuleNoncurrentVersionExpirationArgs{
						NoncurrentDays: pulumi.Int(90),
					},
					NoncurrentVersionTransitions: s3.BucketLifecycleConfigurationRuleNoncurrentVersionTransitionArray{
						&s3.BucketLifecycleConfigurationRuleNoncurrentVersionTransitionArgs{
							NoncurrentDays: pulumi.Int(30),
							StorageClass:   pulumi.String("STANDARD_IA"),
						},
						&s3.BucketLifecycleConfigurationRuleNoncurrentVersionTransitionArgs{
							NoncurrentDays: pulumi.Int(60),
							StorageClass:   pulumi.String("GLACIER"),
						},
					},
					Status: pulumi.String("Enabled"),
				},
			},
		}, pulumi.DependsOn([]pulumi.Resource{
			versioning,
		}))
		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 bucket = new Aws.S3.Bucket("bucket", new()
    {
        BucketName = "my-bucket",
    });

    var bucketAcl = new Aws.S3.BucketAcl("bucket_acl", new()
    {
        Bucket = bucket.BucketName,
        Acl = "private",
    });

    var bucket_config = new Aws.S3.BucketLifecycleConfiguration("bucket-config", new()
    {
        Bucket = bucket.BucketName,
        Rules = new[]
        {
            new Aws.S3.Inputs.BucketLifecycleConfigurationRuleArgs
            {
                Id = "log",
                Expiration = new Aws.S3.Inputs.BucketLifecycleConfigurationRuleExpirationArgs
                {
                    Days = 90,
                },
                Filter = new Aws.S3.Inputs.BucketLifecycleConfigurationRuleFilterArgs
                {
                    And = new Aws.S3.Inputs.BucketLifecycleConfigurationRuleFilterAndArgs
                    {
                        Prefix = "log/",
                        Tags = 
                        {
                            { "rule", "log" },
                            { "autoclean", "true" },
                        },
                    },
                },
                Status = "Enabled",
                Transitions = new[]
                {
                    new Aws.S3.Inputs.BucketLifecycleConfigurationRuleTransitionArgs
                    {
                        Days = 30,
                        StorageClass = "STANDARD_IA",
                    },
                    new Aws.S3.Inputs.BucketLifecycleConfigurationRuleTransitionArgs
                    {
                        Days = 60,
                        StorageClass = "GLACIER",
                    },
                },
            },
            new Aws.S3.Inputs.BucketLifecycleConfigurationRuleArgs
            {
                Id = "tmp",
                Filter = new Aws.S3.Inputs.BucketLifecycleConfigurationRuleFilterArgs
                {
                    Prefix = "tmp/",
                },
                Expiration = new Aws.S3.Inputs.BucketLifecycleConfigurationRuleExpirationArgs
                {
                    Date = "2023-01-13T00:00:00Z",
                },
                Status = "Enabled",
            },
        },
    });

    var versioningBucket = new Aws.S3.Bucket("versioning_bucket", new()
    {
        BucketName = "my-versioning-bucket",
    });

    var versioningBucketAcl = new Aws.S3.BucketAcl("versioning_bucket_acl", new()
    {
        Bucket = versioningBucket.BucketName,
        Acl = "private",
    });

    var versioning = new Aws.S3.BucketVersioning("versioning", new()
    {
        Bucket = versioningBucket.BucketName,
        VersioningConfiguration = new Aws.S3.Inputs.BucketVersioningVersioningConfigurationArgs
        {
            Status = "Enabled",
        },
    });

    var versioning_bucket_config = new Aws.S3.BucketLifecycleConfiguration("versioning-bucket-config", new()
    {
        Bucket = versioningBucket.BucketName,
        Rules = new[]
        {
            new Aws.S3.Inputs.BucketLifecycleConfigurationRuleArgs
            {
                Id = "config",
                Filter = new Aws.S3.Inputs.BucketLifecycleConfigurationRuleFilterArgs
                {
                    Prefix = "config/",
                },
                NoncurrentVersionExpiration = new Aws.S3.Inputs.BucketLifecycleConfigurationRuleNoncurrentVersionExpirationArgs
                {
                    NoncurrentDays = 90,
                },
                NoncurrentVersionTransitions = new[]
                {
                    new Aws.S3.Inputs.BucketLifecycleConfigurationRuleNoncurrentVersionTransitionArgs
                    {
                        NoncurrentDays = 30,
                        StorageClass = "STANDARD_IA",
                    },
                    new Aws.S3.Inputs.BucketLifecycleConfigurationRuleNoncurrentVersionTransitionArgs
                    {
                        NoncurrentDays = 60,
                        StorageClass = "GLACIER",
                    },
                },
                Status = "Enabled",
            },
        },
    }, new CustomResourceOptions
    {
        DependsOn =
        {
            versioning,
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.s3.Bucket;
import com.pulumi.aws.s3.BucketArgs;
import com.pulumi.aws.s3.BucketAcl;
import com.pulumi.aws.s3.BucketAclArgs;
import com.pulumi.aws.s3.BucketLifecycleConfiguration;
import com.pulumi.aws.s3.BucketLifecycleConfigurationArgs;
import com.pulumi.aws.s3.inputs.BucketLifecycleConfigurationRuleArgs;
import com.pulumi.aws.s3.inputs.BucketLifecycleConfigurationRuleExpirationArgs;
import com.pulumi.aws.s3.inputs.BucketLifecycleConfigurationRuleFilterArgs;
import com.pulumi.aws.s3.inputs.BucketLifecycleConfigurationRuleFilterAndArgs;
import com.pulumi.aws.s3.BucketVersioning;
import com.pulumi.aws.s3.BucketVersioningArgs;
import com.pulumi.aws.s3.inputs.BucketVersioningVersioningConfigurationArgs;
import com.pulumi.aws.s3.inputs.BucketLifecycleConfigurationRuleNoncurrentVersionExpirationArgs;
import com.pulumi.resources.CustomResourceOptions;
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 bucket = new Bucket("bucket", BucketArgs.builder()
            .bucket("my-bucket")
            .build());

        var bucketAcl = new BucketAcl("bucketAcl", BucketAclArgs.builder()
            .bucket(bucket.bucket())
            .acl("private")
            .build());

        var bucket_config = new BucketLifecycleConfiguration("bucket-config", BucketLifecycleConfigurationArgs.builder()
            .bucket(bucket.bucket())
            .rules(            
                BucketLifecycleConfigurationRuleArgs.builder()
                    .id("log")
                    .expiration(BucketLifecycleConfigurationRuleExpirationArgs.builder()
                        .days(90)
                        .build())
                    .filter(BucketLifecycleConfigurationRuleFilterArgs.builder()
                        .and(BucketLifecycleConfigurationRuleFilterAndArgs.builder()
                            .prefix("log/")
                            .tags(Map.ofEntries(
                                Map.entry("rule", "log"),
                                Map.entry("autoclean", "true")
                            ))
                            .build())
                        .build())
                    .status("Enabled")
                    .transitions(                    
                        BucketLifecycleConfigurationRuleTransitionArgs.builder()
                            .days(30)
                            .storageClass("STANDARD_IA")
                            .build(),
                        BucketLifecycleConfigurationRuleTransitionArgs.builder()
                            .days(60)
                            .storageClass("GLACIER")
                            .build())
                    .build(),
                BucketLifecycleConfigurationRuleArgs.builder()
                    .id("tmp")
                    .filter(BucketLifecycleConfigurationRuleFilterArgs.builder()
                        .prefix("tmp/")
                        .build())
                    .expiration(BucketLifecycleConfigurationRuleExpirationArgs.builder()
                        .date("2023-01-13T00:00:00Z")
                        .build())
                    .status("Enabled")
                    .build())
            .build());

        var versioningBucket = new Bucket("versioningBucket", BucketArgs.builder()
            .bucket("my-versioning-bucket")
            .build());

        var versioningBucketAcl = new BucketAcl("versioningBucketAcl", BucketAclArgs.builder()
            .bucket(versioningBucket.bucket())
            .acl("private")
            .build());

        var versioning = new BucketVersioning("versioning", BucketVersioningArgs.builder()
            .bucket(versioningBucket.bucket())
            .versioningConfiguration(BucketVersioningVersioningConfigurationArgs.builder()
                .status("Enabled")
                .build())
            .build());

        var versioning_bucket_config = new BucketLifecycleConfiguration("versioning-bucket-config", BucketLifecycleConfigurationArgs.builder()
            .bucket(versioningBucket.bucket())
            .rules(BucketLifecycleConfigurationRuleArgs.builder()
                .id("config")
                .filter(BucketLifecycleConfigurationRuleFilterArgs.builder()
                    .prefix("config/")
                    .build())
                .noncurrentVersionExpiration(BucketLifecycleConfigurationRuleNoncurrentVersionExpirationArgs.builder()
                    .noncurrentDays(90)
                    .build())
                .noncurrentVersionTransitions(                
                    BucketLifecycleConfigurationRuleNoncurrentVersionTransitionArgs.builder()
                        .noncurrentDays(30)
                        .storageClass("STANDARD_IA")
                        .build(),
                    BucketLifecycleConfigurationRuleNoncurrentVersionTransitionArgs.builder()
                        .noncurrentDays(60)
                        .storageClass("GLACIER")
                        .build())
                .status("Enabled")
                .build())
            .build(), CustomResourceOptions.builder()
                .dependsOn(versioning)
                .build());

    }
}
resources:
  bucket:
    type: aws:s3:Bucket
    properties:
      bucket: my-bucket
  bucketAcl:
    type: aws:s3:BucketAcl
    name: bucket_acl
    properties:
      bucket: ${bucket.bucket}
      acl: private
  bucket-config:
    type: aws:s3:BucketLifecycleConfiguration
    properties:
      bucket: ${bucket.bucket}
      rules:
        - id: log
          expiration:
            days: 90
          filter:
            and:
              prefix: log/
              tags:
                rule: log
                autoclean: 'true'
          status: Enabled
          transitions:
            - days: 30
              storageClass: STANDARD_IA
            - days: 60
              storageClass: GLACIER
        - id: tmp
          filter:
            prefix: tmp/
          expiration:
            date: 2023-01-13T00:00:00Z
          status: Enabled
  versioningBucket:
    type: aws:s3:Bucket
    name: versioning_bucket
    properties:
      bucket: my-versioning-bucket
  versioningBucketAcl:
    type: aws:s3:BucketAcl
    name: versioning_bucket_acl
    properties:
      bucket: ${versioningBucket.bucket}
      acl: private
  versioning:
    type: aws:s3:BucketVersioning
    properties:
      bucket: ${versioningBucket.bucket}
      versioningConfiguration:
        status: Enabled
  versioning-bucket-config:
    type: aws:s3:BucketLifecycleConfiguration
    properties:
      bucket: ${versioningBucket.bucket}
      rules:
        - id: config
          filter:
            prefix: config/
          noncurrentVersionExpiration:
            noncurrentDays: 90
          noncurrentVersionTransitions:
            - noncurrentDays: 30
              storageClass: STANDARD_IA
            - noncurrentDays: 60
              storageClass: GLACIER
          status: Enabled
    options:
      dependsOn:
        - ${versioning}

The noncurrentVersionExpiration and noncurrentVersionTransitions properties manage old versions separately from current ones. Noncurrent versions transition to STANDARD_IA after 30 days, then GLACIER after 60, and expire after 90. Current versions follow the transitions and expiration rules. The dependsOn ensures versioning is enabled before applying lifecycle rules; without versioning, noncurrent version properties have no effect.

Beyond these examples

These snippets focus on specific lifecycle rule features: filter targeting, storage class transitions and expiration, and versioned object lifecycle management. They’re intentionally minimal rather than full bucket configurations.

The examples reference pre-existing infrastructure such as S3 buckets and BucketVersioning resources for version-aware examples. They focus on configuring lifecycle rules rather than provisioning buckets or enabling features.

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

  • Abort incomplete multipart uploads (abortIncompleteMultipartUpload)
  • Date-based expiration (expiration.date)
  • Multiple rules with different prefixes in one configuration
  • ExpiredObjectDeleteMarker cleanup

These omissions are intentional: the goal is to illustrate how each lifecycle feature is wired, not provide drop-in storage management modules. See the S3 Bucket Lifecycle Configuration resource reference for all available configuration options.

Let's configure AWS S3 Bucket Lifecycle Rules

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

Try Pulumi Cloud for FREE

Frequently Asked Questions

Common Pitfalls & Limitations
Why am I seeing perpetual differences in my lifecycle configuration?
S3 buckets only support a single lifecycle configuration. Declaring multiple BucketLifecycleConfiguration resources for the same bucket causes perpetual differences. Use one resource with multiple rules instead.
Why aren't my objects smaller than 128 KB transitioning to other storage classes?
Amazon S3 prevents objects smaller than 128 KB from transitioning by default. To allow smaller objects, add a filter with objectSizeGreaterThan: 1 to your rule.
Why am I getting idempotence issues after creating a lifecycle configuration?
Lifecycle configurations take time to propagate across all AWS S3 systems. Running Pulumi operations immediately after creation may cause idempotence issues. Allow time for propagation before subsequent operations.
Filtering & Rule Scope
How do I apply lifecycle rules to all objects in a bucket?
Either omit the filter property entirely or set filter: {}. Both approaches apply the rule to all objects in the bucket.
How do I filter lifecycle rules by prefix?
Set filter.prefix to your desired prefix (e.g., logs/). To apply different rules to different prefixes, create separate rules with different prefix values.
How do I filter lifecycle rules by object tags?
For a single tag, use filter.tag with key and value. For multiple tags, wrap them in filter.and.tags.
Can I combine prefix and tag filters in the same rule?
Yes, wrap both prefix and tags in the filter.and configuration block to apply the rule only to objects matching both criteria.
How do I filter by object size?
Use objectSizeGreaterThan for minimum size or objectSizeLessThan for maximum size (values in bytes, max 5TB). For a size range, use both within filter.and, ensuring objectSizeGreaterThan is less than objectSizeLessThan.
Versioning & Noncurrent Objects
How do I manage lifecycle rules for versioned buckets?
Use noncurrentVersionExpiration to expire old versions and noncurrentVersionTransitions to transition them to different storage classes. Add dependsOn: [versioning] to ensure versioning is enabled before applying lifecycle rules.
Configuration & Defaults
What does the `transitionDefaultMinimumObjectSize` property do?
It sets the default minimum object size behavior for transitions. Valid values are all_storage_classes_128K (default) and varies_by_storage_class. Custom filters with objectSizeGreaterThan or objectSizeLessThan always take precedence over this default.
Is the `expectedBucketOwner` property still supported?
Yes, but it’s deprecated and will be removed in a future version of the provider. Avoid using it in new configurations.

Using a different cloud?

Explore storage guides for other cloud providers: