Lifecycle rules
Automate object expiration, the cleanup of old versions, and the abort of incomplete multipart uploads in your OtterStorage buckets.
Lifecycle rules let OtterStorage delete objects, noncurrent versions, and incomplete multipart uploads automatically, based on their age and prefix. With the S3-compatible API you define the configuration once and OtterStorage takes care of applying it every day, without your having to run manual cleanups. With OtterStorage we don't charge for requests or deletions, so these rules only save you storage—they never add cost for the deletion operations.
How it works
A lifecycle configuration is a set of rules. Each rule has an identifier, a status (Enabled or Disabled), a filter that decides which objects it applies to (usually by prefix), and one or more actions. OtterStorage evaluates the rules periodically and runs the actions on the objects that meet the conditions.
The most common actions are:
- Expiration: expires (deletes) objects when they exceed a given age in days.
- NoncurrentVersionExpiration: expires noncurrent versions (the old versions that versioning leaves behind when an object is overwritten or deleted).
- AbortIncompleteMultipartUpload: aborts multipart uploads that were never completed and frees the space taken by their parts.
Age is always counted in whole days from the object's creation (or from when the version stopped being the current one). Actions run asynchronously, so an object may take a few hours to disappear after meeting its condition.
Expiring objects by prefix and age
The most common case: automatically deleting the contents of a logical folder after a few days. The prefix is the initial part of the object's key (for example tmp/ or logs/2024/), and Expiration.Days indicates the number of days after which the object expires.
This rule expires everything under tmp/ after 30 days:
{
"Rules": [
{
"ID": "expirar-tmp-30d",
"Status": "Enabled",
"Filter": { "Prefix": "tmp/" },
"Expiration": { "Days": 30 }
}
]
}
If you want to apply the rule to the entire bucket, use an empty prefix with "Filter": { "Prefix": "" }. To limit the rule to objects above or below a certain size, you can add ObjectSizeGreaterThan or ObjectSizeLessThan inside the filter.
Expiring noncurrent versions
If the bucket has versioning enabled, every time you overwrite or delete an object the previous version is kept as a noncurrent version. These versions still take up storage. With NoncurrentVersionExpiration you define how many days you want to keep them before deleting them.
This rule cleans up noncurrent versions 90 days after they stop being the current version:
{
"Rules": [
{
"ID": "limpiar-versiones-90d",
"Status": "Enabled",
"Filter": { "Prefix": "" },
"NoncurrentVersionExpiration": { "NoncurrentDays": 90 }
}
]
}
The NoncurrentDays field counts from the moment the version stops being the current one, not from when it was created. Optionally you can add NewerNoncurrentVersions to always keep the N most recent versions and expire only the ones below that threshold; for example, keeping the last 3 and deleting the rest after 90 days.
Aborting incomplete multipart uploads
Large uploads are done in several parts (multipart upload). If an upload is interrupted and never completed or aborted, its parts remain stored and take up space without forming any visible object. The AbortIncompleteMultipartUpload action cleans them up automatically.
This rule aborts any multipart upload that has gone more than 7 days without completing:
{
"Rules": [
{
"ID": "abortar-multipart-7d",
"Status": "Enabled",
"Filter": { "Prefix": "" },
"AbortIncompleteMultipartUpload": { "DaysAfterInitiation": 7 }
}
]
}
It's one of the most cost-effective rules: it reclaims space that would otherwise pile up silently. We recommend including it in virtually every bucket.
Complete example configuration
It's normal to combine several actions in a single configuration. The following lifecycle.json brings together the three previous examples: it expires tmp/ after 30 days, cleans up noncurrent versions after 90 days, and aborts incomplete multipart uploads after 7 days.
{
"Rules": [
{
"ID": "expirar-tmp-30d",
"Status": "Enabled",
"Filter": { "Prefix": "tmp/" },
"Expiration": { "Days": 30 }
},
{
"ID": "limpiar-versiones-90d",
"Status": "Enabled",
"Filter": { "Prefix": "" },
"NoncurrentVersionExpiration": { "NoncurrentDays": 90 }
},
{
"ID": "abortar-multipart-7d",
"Status": "Enabled",
"Filter": { "Prefix": "" },
"AbortIncompleteMultipartUpload": { "DaysAfterInitiation": 7 }
}
]
}
Each rule is independent: you can combine different actions in the same rule (for example Expiration and AbortIncompleteMultipartUpload together if they share a filter) or keep them separate, as in this example, so they're easier to read and maintain.
Applying the configuration with the AWS CLI
Save the JSON above in a file called lifecycle.json and apply it with put-bucket-lifecycle-configuration. Make sure you have the OtterStorage endpoint configured; see the AWS CLI guide for the credential and profile details.
aws s3api put-bucket-lifecycle-configuration \
--endpoint-url https://es-mad-1.s3.otterstorage.io \
--region eu-mad \
--bucket mi-bucket \
--lifecycle-configuration file://lifecycle.json
The command returns no output on success: it completely replaces the bucket's lifecycle configuration with the one you send. To query the active configuration at any time, use get-bucket-lifecycle-configuration:
aws s3api get-bucket-lifecycle-configuration \
--endpoint-url https://es-mad-1.s3.otterstorage.io \
--region eu-mad \
--bucket mi-bucket
The response is the same JSON of rules you applied. To remove all rules from a bucket, use delete-bucket-lifecycle with the same endpoint, region, and bucket.
Cost best practices
Lifecycle is the most direct tool for keeping storage under control. Some recommendations:
- Always abort incomplete multipart uploads. An
AbortIncompleteMultipartUploadrule at 7 days on every bucket keeps orphaned, invisible parts from accumulating. - Expire ephemeral data by prefix. Place temporary data under prefixes like
tmp/,cache/, orlogs/and apply short expiration to them. A good naming convention makes the rules trivial. - Limit version history. If you use versioning,
NoncurrentVersionExpirationkeeps old versions from growing without limit. Combine it withNewerNoncurrentVersionsto always keep a few recent versions. - Start with
Disabledand verify. If you're unsure about a rule's scope, create it with"Status": "Disabled"or with generous timeframes, check which objects match, and then adjust it. Remember that expirations are irreversible. - Review periodically. Use
get-bucket-lifecycle-configurationto audit the active rules and remove the ones that no longer apply.
Because we don't charge for requests or deletions, the only cost factor is the storage taken up: the sooner what you don't need expires, the less you pay. To protect buckets that must not be touched, see Legal Hold, which blocks any change to a bucket's lifecycle configuration while it's active.
Ready to try it out?
Create your account and get your keys in minutes.