Skip to main content

Lifecycle policies

Every STRG bucket is versioned, all the time, which makes a good safety net: overwriting an object keeps the old version, and deleting an object writes a delete marker and keeps every version behind it. All of those versions are billed as stored data until something removes them.

Lifecycle rules are that something. A rule tells us how long to keep things under a key prefix, and we tidy up on schedule, so old data leaves the bucket - and the bill - without anyone having to remember it. Without rules, objects and their previous versions are kept until you delete them.

Some situations where a rule earns its place:

  • A nightly backup that overwrites the same keys every night: a rule keeps a month of previous versions to restore from, and lets older ones go.
  • Logs or scratch data under logs/ or tmp/, useful for a few days and then not at all.
  • Large uploads from an app or an agent that sometimes stop halfway: a rule clears away the parts of uploads that were never completed, which are otherwise stored and billed.

What a rule does​

A rule applies to every object whose key starts with its prefix, and an empty prefix is the whole bucket. Each rule carries up to three actions - at least one, since a rule without an action would have nothing to do. Here is what each one does, and where to find it in the dashboard and in S3's XML:

ActionDashboard fieldS3 elementEffect
Expire the current versionExpire the current version afterExpiration / DaysN days after an object was uploaded, its current version becomes a previous version and the object stops being listed. The data is still stored and billed until a delete previous versions action removes it
Delete previous versionsPermanently delete previous versions afterNoncurrentVersionExpiration / NoncurrentDaysDeletes a previous version permanently, N days after it stopped being current. This is the action that frees storage
Abort incomplete multipart uploadsAbort incomplete multipart uploads afterAbortIncompleteMultipartUpload / DaysAfterInitiationAborts a multipart upload that was not completed within N days of starting, and discards its parts

A few details about how the actions behave:

  • Days are whole numbers, at least 1.
  • Rules run once a day, so an action happens at the first daily run after its number of days has passed rather than at the exact hour.
  • A rule also tidies up a delete marker once no versions remain behind it.
  • Lifecycle rules cannot remove a locked version. A version under an Object Lock retention period or legal hold is skipped, stays billed, and is removed once its lock has expired. See Alongside Object Lock.
Deleted versions are gone for good

Delete previous versions deletes data permanently. Combined with Expire the current version on the same prefix, it deletes every object under that prefix once both periods have passed - and with an empty prefix, every object in the bucket. Deleted versions cannot be restored.

We suggest two rules on every bucket, because they pay for themselves quietly:

ActionSuggested daysWhy
Permanently delete previous versions after14Versioning is always on, so every overwrite and every delete leaves a previous version behind, and that version is billed. This action is the only thing that frees it
Abort incomplete multipart uploads after7An upload that stopped halfway leaves parts that are stored and billed until something clears them away

Those numbers are a starting point, and a different one fits when the use case is known:

  • Behind a backup tool that prunes its own data, such as Kopia or restic: 1 day for previous versions. These tools delete long after they wrote the data, so by the time a version stops being current, the tool has already decided it is no longer needed.
  • On scratch prefixes such as tmp/: 1 day for both, as in the tmp/ recipe.
  • For files people edit by hand, a longer window, such as the 30 days in the first recipe, leaves more time to notice and recover a mistake.

The days for previous versions count from the day the version stopped being current - the day it was overwritten or deleted - and not from the day it was uploaded. A file that sat untouched for a year and is overwritten today still has its previous version for the full number of days.

Alongside Object Lock​

On a bucket with Object Lock, the two clocks are independent. A lock runs from the upload, a rule's days run from the moment a version stopped being current, and a rule never removes a version that is still locked, whatever its days are. That version is skipped and removed once its lock expires. The number of days therefore does not need to match the retention period, and setting them equal is not the aim: the lock protects the version, and the rule tidies up after it.

Limits​

STRG checks each rule as it is saved, so a mistake shows up straight away rather than as a surprise weeks later. These are the bounds a bucket's rules stay within:

LimitValue
Rules per bucket100
Rules per prefix1. All actions for one prefix go in one rule
Overlapping prefixesKept apart: no prefix may start with another (logs/ and logs/app/ overlap), and a rule for the whole bucket must be the only rule
Prefix length1,024 bytes of UTF-8. No control characters
Rule ID (S3 API)255 characters, unique within the bucket - including the _marker suffix described under Rule IDs. No control characters

Amazon S3 allows overlapping prefixes. STRG keeps them apart, which makes rules easy to read: every object falls under at most one rule, so it is always clear which days apply to it.

In the dashboard​

The dashboard is the quickest way to set up a rule, and it shows the bucket's rules in plain words. Here is how it goes:

  1. A bucket's settings live on its page in the dashboard at https://app.strg.com: on a narrower screen they are the Settings tab, and on a wide one they sit beside the object browser.
  2. The Lifecycle rules card lists the bucket's rules, and its Edit button opens the editor.
  3. Add a rule adds one. Its Objects whose key starts with field takes a prefix such as logs/; left empty, the rule covers the whole bucket.
  4. Each action takes a number of days, and an empty field means Never.
  5. Save rules saves them.

Anything that needs a second look - an overlap, a rule with no action - is shown right under the rule, and Save rules waits until it is sorted out. Saving replaces every rule on the bucket, so saving with no rules removes them all.

The Ask our AI Assistant can fill in this form for you from a description such as "keep old versions for 30 days"; pressing Save rules stays with you.

The dashboard and the S3 API edit the same configuration, so rules set through one show up in the other.

A configuration the dashboard cannot show​

If a bucket's configuration contains something the form has no field for, the card shows a warning naming it and offers no Edit button, and that bucket's rules are managed through the S3 API instead. This keeps your rules safe: showing only part of the configuration would mean a save from the dashboard deletes the rules it did not show.

Through the S3 API​

Three operations read and write a bucket's rules, each with the access key permission it needs:

OperationNeeds the permission
PutBucketLifecycleConfigurationManage lifecycle rules (bucket-lifecycle:write)
GetBucketLifecycleConfigurationRead lifecycle rules (bucket-lifecycle:read)
DeleteBucketLifecycleManage lifecycle rules (bucket-lifecycle:write)

Both permissions can be held by a key scoped to specific buckets; Access keys has the details. When an agent works through the MCP server with a key that does not hold Act without confirmation (mcp:skip-confirmation), we ask you to confirm before it replaces a bucket's lifecycle rules, since a rule can delete data permanently.

PutBucketLifecycleConfiguration replaces the bucket's whole configuration. This one expires everything under tmp/ after a day, and keeps previous versions under backups/ for 30 days:

lifecycle.xml
<?xml version="1.0" encoding="UTF-8"?>
<LifecycleConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<Rule>
<ID>backups</ID>
<Filter><Prefix>backups/</Prefix></Filter>
<Status>Enabled</Status>
<NoncurrentVersionExpiration><NoncurrentDays>30</NoncurrentDays></NoncurrentVersionExpiration>
<AbortIncompleteMultipartUpload><DaysAfterInitiation>7</DaysAfterInitiation></AbortIncompleteMultipartUpload>
</Rule>
<Rule>
<ID>tmp</ID>
<Filter><Prefix>tmp/</Prefix></Filter>
<Status>Enabled</Status>
<Expiration><Days>1</Days></Expiration>
<NoncurrentVersionExpiration><NoncurrentDays>1</NoncurrentDays></NoncurrentVersionExpiration>
<AbortIncompleteMultipartUpload><DaysAfterInitiation>1</DaysAfterInitiation></AbortIncompleteMultipartUpload>
</Rule>
</LifecycleConfiguration>

STRG needs the request to carry Content-MD5 or an x-amz-checksum-* header, and answers 400 InvalidRequest without one. The AWS CLI and the AWS SDKs add one automatically. Another HTTP client sends Content-MD5, the base64 MD5 digest of the body (openssl dgst -md5 -binary lifecycle.xml | openssl base64).

The same configuration from each tool:

lifecycle.json
{
"Rules": [
{
"ID": "backups",
"Filter": { "Prefix": "backups/" },
"Status": "Enabled",
"NoncurrentVersionExpiration": { "NoncurrentDays": 30 },
"AbortIncompleteMultipartUpload": { "DaysAfterInitiation": 7 }
},
{
"ID": "tmp",
"Filter": { "Prefix": "tmp/" },
"Status": "Enabled",
"Expiration": { "Days": 1 },
"NoncurrentVersionExpiration": { "NoncurrentDays": 1 },
"AbortIncompleteMultipartUpload": { "DaysAfterInitiation": 1 }
}
]
}
aws s3api put-bucket-lifecycle-configuration \
--bucket my-bucket \
--lifecycle-configuration file://lifecycle.json \
--endpoint-url https://s3.eu-central-1.strg.com \
--region eu-central-1

aws s3api get-bucket-lifecycle-configuration \
--bucket my-bucket \
--endpoint-url https://s3.eu-central-1.strg.com \
--region eu-central-1

DeleteBucketLifecycle removes every rule. A bucket with no rules answers GetBucketLifecycleConfiguration with 404 NoSuchLifecycleConfiguration, as Amazon S3 does.

Rule IDs​

A client that keeps track of its rules by ID finds them where it left them:

  • A rule keeps the ID you gave it, including when the rules are later saved from the dashboard.
  • A rule without an ID, or added in the dashboard, gets one generated from its prefix, starting with strg-.
  • A rule with Expiration reads back as two rules. STRG adds a companion rule on the same prefix carrying ExpiredObjectDeleteMarker, with the ID <ID>_marker - for the example above, tmp_marker. There is no need to send it yourself, and a configuration you read back can be sent again unchanged: a companion rule is welcome beside an Expiration with Days on the same prefix, and is replaced by the generated one.

What is supported​

A rule holds a Filter with a Prefix, Status Enabled, and the three actions above. For anything beyond that, STRG answers 501 NotImplemented and leaves the bucket's rules exactly as they were, so a client knows straight away:

In the configurationAnswer
Transition, NoncurrentVersionTransition501 NotImplemented
Expiration with Date instead of Days501 NotImplemented
NewerNoncurrentVersions501 NotImplemented
A Filter with Tag, And, ObjectSizeGreaterThan or ObjectSizeLessThan501 NotImplemented
Status Disabled501 NotImplemented. A rule that is not wanted can simply be removed
A number of days that is not a whole number of at least 1501 NotImplemented
An ExpiredObjectDeleteMarker rule without an Expiration in Days on the same prefix, or ExpiredObjectDeleteMarker false501 NotImplemented
A top-level Prefix in a Rule - the deprecated PutBucketLifecycle form501 NotImplemented. Filter / Prefix carries the same prefix

Errors​

When a configuration cannot be saved, nothing on the bucket changes and the answer says why:

ErrorWhen
400 InvalidRequestNo Content-MD5 or x-amz-checksum-* header; or the rules break a limit - overlapping prefixes, two rules for one prefix, more than 100 rules. The message names the problem
400 InvalidArgumentA rule ID is longer than 255 characters, _marker included, or two rules share one
400 MalformedXMLThe body is not a LifecycleConfiguration, has no rule with an action, or contains comments, CDATA sections, a DOCTYPE or namespace prefixes
403 AccessDeniedThe access key lacks the permission for the operation
501 NotImplementedA feature listed under What is supported

Recipes​

A few rules that come up again and again. Each recipe is one rule. A rule for the whole bucket must be the only rule, so recipes combine by putting all their actions in one whole-bucket rule, or by giving each its own prefix.

Keeping 30 days of old versions​

A good fit for backups and for anything people edit: overwritten and deleted objects stay recoverable for 30 days, then their previous versions are deleted. Current versions are never touched.

PrefixExpire currentDelete previous versionsAbort uploads
(empty)-30 days-
{ "ID": "keep-30-days", "Filter": { "Prefix": "" }, "Status": "Enabled",
"NoncurrentVersionExpiration": { "NoncurrentDays": 30 } }

Cleaning up abandoned multipart uploads​

Parts of uploads that were never completed are stored and billed, and an upload interrupted by a dropped connection or a stopped process can leave some behind. This rule aborts any upload still incomplete a week after it started.

PrefixExpire currentDelete previous versionsAbort uploads
(empty)--7 days
{ "ID": "abort-uploads", "Filter": { "Prefix": "" }, "Status": "Enabled",
"AbortIncompleteMultipartUpload": { "DaysAfterInitiation": 7 } }

Both recipes together, as the bucket's only rule:

{ "ID": "whole-bucket", "Filter": { "Prefix": "" }, "Status": "Enabled",
"NoncurrentVersionExpiration": { "NoncurrentDays": 30 },
"AbortIncompleteMultipartUpload": { "DaysAfterInitiation": 7 } }

Expiring a tmp/ prefix​

For scratch space - build artefacts, an agent's working files, uploads waiting to be processed: everything under tmp/ disappears from listings a day after upload and is deleted a day later. Expiring the current version alone would keep it billed as a previous version; the second action is what deletes it.

PrefixExpire currentDelete previous versionsAbort uploads
tmp/1 day1 day1 day
{ "ID": "tmp", "Filter": { "Prefix": "tmp/" }, "Status": "Enabled",
"Expiration": { "Days": 1 },
"NoncurrentVersionExpiration": { "NoncurrentDays": 1 },
"AbortIncompleteMultipartUpload": { "DaysAfterInitiation": 1 } }

Each JSON rule goes in the Rules array of lifecycle.json in the AWS CLI example.

View as Markdown