What lifecycle policies do
A lifecycle policy is a set of rules attached to one bucket. Once a day, Object Storage applies them:
| Action | What it does | Typical use |
|---|---|---|
INFREQUENT_ACCESS | Moves objects to the Infrequent Access tier | Logs and exports read rarely after a month |
ARCHIVE | Moves objects to Archive | Long-term retention, compliance copies |
DELETE | Deletes objects or previous versions | Temporary data, old versions |
ABORT | Cancels uncommitted multipart uploads | Clean up failed large uploads |
Each rule has a target: current objects, previous object versions (versioned buckets only) or multipart uploads, plus an optional name filter.
Step 1: the service IAM policy
Lifecycle actions are performed by the Object Storage service, so it needs permission. Without this policy, rules save fine but never run.
Create it in the root compartment, once per region you use:
Allow service objectstorage-eu-frankfurt-1 to manage object-family in compartment Prod-Data
Least-privilege variant:
Allow service objectstorage-eu-frankfurt-1 to manage object-family in compartment Prod-Data
where any {request.permission='BUCKET_INSPECT', request.permission='BUCKET_READ',
request.permission='OBJECT_INSPECT', request.permission='OBJECT_UPDATE_TIER',
request.permission='OBJECT_DELETE', request.permission='OBJECT_VERSION_DELETE'}
Use the region identifier (eu-frankfurt-1, us-ashburn-1, ...), not the short key.
Step 2: write the rules file
lifecycle.json:
[
{
"name": "logs-to-ia-30d",
"action": "INFREQUENT_ACCESS",
"timeAmount": 30,
"timeUnit": "DAYS",
"isEnabled": true,
"target": "objects",
"objectNameFilter": { "inclusionPrefixes": ["logs/"] }
},
{
"name": "exports-archive-90d",
"action": "ARCHIVE",
"timeAmount": 90,
"timeUnit": "DAYS",
"isEnabled": true,
"target": "objects",
"objectNameFilter": {
"inclusionPrefixes": ["exports/"],
"exclusionPatterns": ["*.tmp"]
}
},
{
"name": "delete-old-versions-60d",
"action": "DELETE",
"timeAmount": 60,
"timeUnit": "DAYS",
"isEnabled": true,
"target": "previous-object-versions"
},
{
"name": "abort-stale-multipart-7d",
"action": "ABORT",
"timeAmount": 7,
"timeUnit": "DAYS",
"isEnabled": true,
"target": "multipart-uploads"
}
]
Filter rules: prefixes match the full object name including "folder" paths; patterns support *, ? and [a-f]. Exclusion patterns are applied first, then inclusion patterns, then prefixes. Leave the filter out to apply a rule to everything.
Step 3: apply it
NS=$(oci os ns get --query data --raw-output)
# See what is there now (save it!)
oci os object-lifecycle-policy get --namespace $NS --bucket-name app-data \
> lifecycle-before.json
oci os object-lifecycle-policy put --namespace $NS --bucket-name app-data \
--items file://lifecycle.json
Caution
putreplaces the whole policy. Any rule not in your file is removed. Alwaysgetfirst, merge the existing rules into your file, thenput. Keep the file in Git so the bucket's policy has a history.
Remove all rules from a bucket:
oci os object-lifecycle-policy delete --namespace $NS --bucket-name app-data --force
Step 4: check it works
Lifecycle runs roughly once a day, and changes can take up to a day to be picked up. Object age is counted from the last modification time (UTC), rounded up to whole days.
Check tiers on a sample after a day or two:
oci os object list --namespace $NS --bucket-name app-data --prefix logs/ \
--fields name,timeModified,storageTier --limit 20 \
--query 'data[].{name:name, modified:"time-modified", tier:"storage-tier"}' --output table
Count uncommitted multipart uploads before and after:
oci os multipart list --namespace $NS --bucket-name app-data --all \
--query 'length(data)'
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| Rules saved, nothing changes after 2+ days | Service IAM policy missing or wrong region identifier | Add the objectstorage-<region> policy in the root compartment |
| Some objects never move | Name filter doesn't match (missing trailing /, case) | Test the prefix with oci os object list --prefix |
| Older rules disappeared | A later put replaced the policy | Restore from your saved JSON / Git |
| Previous versions not deleted | Bucket versioning never enabled, or target is objects | Use previous-object-versions on a versioned bucket |
| Delete action fails on some objects | Retention rule on the bucket locks them | Expected; retention rules win, adjust one or the other |
| Unexpected retrieval charges | Archived objects read back by an app | Archive only data nobody reads; check costs before tiering |
Warning
Objects deleted by a lifecycle rule cannot be recovered. Test new
DELETErules on a non-production bucket, or start with a longtimeAmountand shorten it later.
Prevent it next time
- Put an
ABORTrule for multipart uploads on every bucket; failed uploads are invisible storage cost. - Turn on versioning only together with a
previous-object-versionsdelete rule. - Manage lifecycle policies in Terraform (
oci_objectstorage_object_lifecycle_policy) so a manualputcan't silently drop rules. - Review tiering choices against current OCI storage pricing; Archive has a minimum retention period and restore delays.