Configure AWS S3 Bucket Lifecycle Rules

The aws:s3/bucketLifecycleConfigurationV2:BucketLifecycleConfigurationV2 resource, part of the Pulumi AWS provider, defines lifecycle rules that automatically transition objects to cheaper storage classes or delete them after specified periods. This guide focuses on three capabilities: filtering objects by prefix, tags, and size; configuring transitions and expiration; and managing versioned object lifecycles.

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

Apply rules to objects by prefix

Most lifecycle policies target specific paths where predictable data accumulates, like log files or temporary uploads.

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

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

example = aws.s3.BucketLifecycleConfiguration("example",
    bucket=bucket["id"],
    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.Id),
			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.Id,
        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.id())
            .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.id}
      rules:
        - id: rule-1
          filter:
            prefix: logs/
          status: Enabled

The filter property narrows which objects the rule affects. Setting prefix to “logs/” means only objects whose keys start with that path match this rule. The id property names the rule for tracking, and status must be “Enabled” for the rule to take effect.

Target objects by tag key and value

Applications that tag objects with metadata can apply lifecycle rules based on those tags instead of path structure.

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

const example = new aws.s3.BucketLifecycleConfiguration("example", {
    bucket: bucket.id,
    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["id"],
    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.Id),
			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.Id,
        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.id())
            .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.id}
      rules:
        - id: rule-1
          filter:
            tag:
              key: Name
              value: Staging
          status: Enabled

The tag property inside filter specifies both a key and value that objects must have. This rule applies only to objects tagged with Name=Staging, regardless of their location in the bucket.

Combine prefix and tag filters with AND logic

When objects must match multiple criteria, the and block requires both a prefix and one or more tags.

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

const example = new aws.s3.BucketLifecycleConfiguration("example", {
    bucket: bucket.id,
    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["id"],
    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.Id),
			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.Id,
        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.id())
            .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.id}
      rules:
        - id: rule-1
          filter:
            and:
              prefix: logs/
              tags:
                Key1: Value1
                Key2: Value2
          status: Enabled

The and block wraps both prefix and tags, creating a compound filter. Objects must be under “logs/” AND have both Key1=Value1 and Key2=Value2 to match. This extends basic filtering by combining location and metadata requirements.

Override default size thresholds for transitions

S3 prevents objects smaller than 128 KB from transitioning by default. You can override this with a minimum size filter.

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

const example = new aws.s3.BucketLifecycleConfiguration("example", {
    bucket: bucket.id,
    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["id"],
    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.Id),
			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.Id,
        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.id())
            .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.id}
      rules:
        - id: Allow small object transitions
          filter:
            objectSizeGreaterThan: 1
          status: Enabled
          transitions:
            - days: 365
              storageClass: GLACIER_IR

Setting objectSizeGreaterThan to 1 byte allows even tiny objects to transition. The transitions array defines when objects move to cheaper storage classes. Here, objects transition to GLACIER_IR after 365 days, regardless of size.

Manage current and noncurrent object versions

Versioned buckets accumulate old versions over time. Lifecycle rules can manage noncurrent versions separately from current 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.id,
    acl: "private",
});
const bucket_config = new aws.s3.BucketLifecycleConfiguration("bucket-config", {
    bucket: bucket.id,
    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.id,
    acl: "private",
});
const versioning = new aws.s3.BucketVersioning("versioning", {
    bucket: versioningBucket.id,
    versioningConfiguration: {
        status: "Enabled",
    },
});
const versioning_bucket_config = new aws.s3.BucketLifecycleConfiguration("versioning-bucket-config", {
    bucket: versioningBucket.id,
    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.id,
    acl="private")
bucket_config = aws.s3.BucketLifecycleConfiguration("bucket-config",
    bucket=bucket.id,
    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.id,
    acl="private")
versioning = aws.s3.BucketVersioning("versioning",
    bucket=versioning_bucket.id,
    versioning_configuration={
        "status": "Enabled",
    })
versioning_bucket_config = aws.s3.BucketLifecycleConfiguration("versioning-bucket-config",
    bucket=versioning_bucket.id,
    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.ID(),
			Acl:    pulumi.String("private"),
		})
		if err != nil {
			return err
		}
		_, err = s3.NewBucketLifecycleConfiguration(ctx, "bucket-config", &s3.BucketLifecycleConfigurationArgs{
			Bucket: bucket.ID(),
			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.ID(),
			Acl:    pulumi.String("private"),
		})
		if err != nil {
			return err
		}
		versioning, err := s3.NewBucketVersioning(ctx, "versioning", &s3.BucketVersioningArgs{
			Bucket: versioningBucket.ID(),
			VersioningConfiguration: &s3.BucketVersioningVersioningConfigurationArgs{
				Status: pulumi.String("Enabled"),
			},
		})
		if err != nil {
			return err
		}
		_, err = s3.NewBucketLifecycleConfiguration(ctx, "versioning-bucket-config", &s3.BucketLifecycleConfigurationArgs{
			Bucket: versioningBucket.ID(),
			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.Id,
        Acl = "private",
    });

    var bucket_config = new Aws.S3.BucketLifecycleConfiguration("bucket-config", new()
    {
        Bucket = bucket.Id,
        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.Id,
        Acl = "private",
    });

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

    var versioning_bucket_config = new Aws.S3.BucketLifecycleConfiguration("versioning-bucket-config", new()
    {
        Bucket = versioningBucket.Id,
        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.id())
            .acl("private")
            .build());

        var bucket_config = new BucketLifecycleConfiguration("bucket-config", BucketLifecycleConfigurationArgs.builder()
            .bucket(bucket.id())
            .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.id())
            .acl("private")
            .build());

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

        var versioning_bucket_config = new BucketLifecycleConfiguration("versioning-bucket-config", BucketLifecycleConfigurationArgs.builder()
            .bucket(versioningBucket.id())
            .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.id}
      acl: private
  bucket-config:
    type: aws:s3:BucketLifecycleConfiguration
    properties:
      bucket: ${bucket.id}
      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.id}
      acl: private
  versioning:
    type: aws:s3:BucketVersioning
    properties:
      bucket: ${versioningBucket.id}
      versioningConfiguration:
        status: Enabled
  versioning-bucket-config:
    type: aws:s3:BucketLifecycleConfiguration
    properties:
      bucket: ${versioningBucket.id}
      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 property deletes old versions after a specified number of days. The noncurrentVersionTransitions array moves old versions through storage classes before deletion. Current versions use expiration and transitions properties instead. The dependsOn ensures versioning is enabled before applying lifecycle rules.

Beyond these examples

These snippets focus on specific lifecycle rule features: prefix, tag, and size-based filtering; storage class transitions and expiration; and versioned object lifecycle management. They’re intentionally minimal rather than full data retention policies.

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

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

  • Expiration dates vs day counts (date property)
  • Incomplete multipart upload cleanup (abortIncompleteMultipartUpload)
  • Multiple rules with different filters in one configuration
  • Object size range filters (objectSizeLessThan)

These omissions are intentional: the goal is to illustrate how each lifecycle feature is wired, not provide drop-in retention 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 & Configuration Issues
Why am I seeing perpetual differences in my lifecycle configuration?
S3 buckets only support a single lifecycle configuration. If you declare multiple BucketLifecycleConfiguration resources for the same bucket, it will cause a perpetual difference in configuration. Use only one resource per bucket and define multiple rules within it.
Why aren't my lifecycle configuration changes taking effect immediately?
Lifecycle configurations may take some time to fully propagate to all AWS S3 systems. Running Pulumi operations shortly after creating a lifecycle configuration may result in changes that affect configuration idempotence. Allow time for propagation before running subsequent operations.
Why aren't objects smaller than 128 KB transitioning to other storage classes?
Amazon S3 applies a default behavior that prevents objects smaller than 128 KB from being transitioned. To allow smaller objects to transition, add a filter with objectSizeGreaterThan set to a small value like 1 byte, or configure transitionDefaultMinimumObjectSize to varies_by_storage_class.
Rule Filtering & Targeting
How do I apply a lifecycle rule to all objects in a bucket?
Either omit the filter property entirely, or set filter to an empty object {}. Both approaches have the same effect and apply the rule to all objects in the bucket.
How do I filter lifecycle rules by object prefix?
Use filter.prefix to target objects with a specific key prefix (e.g., logs/). If you need to apply different lifecycle actions to multiple prefixes, create separate rules with different filter.prefix values.
How do I filter lifecycle rules by object tags?
For a single tag, use filter.tag with key and value properties. For multiple tags, wrap them in filter.and.tags as a map of key-value pairs.
How do I combine prefix and tag filters in a lifecycle rule?
Wrap both prefix and tags in a filter.and configuration block. This applies the rule only to objects matching both the prefix and all specified tags.
Versioning & Advanced Filtering
How do I configure lifecycle rules for versioned buckets?
Use noncurrentVersionExpiration and noncurrentVersionTransitions properties to manage noncurrent object versions. Add dependsOn to ensure the bucket versioning configuration is created before the lifecycle configuration.
How do I filter lifecycle rules by object size?
Use objectSizeGreaterThan or objectSizeLessThan in the filter, with values in bytes (maximum 5TB). When using both to create a size range, objectSizeGreaterThan must be less than objectSizeLessThan. For multiple size-based criteria, wrap them in a filter.and block.
Import & Cross-Account Access
How do I import a lifecycle configuration from a bucket in a different AWS account?
Use the format bucket-name,account-id when importing. For buckets in the same account as your provider configuration, you can import using just the bucket name.

Using a different cloud?

Explore storage guides for other cloud providers: