Home / OCI

OCIHow-toIntermediate

OCI Object Storage lifecycle policies: archive, delete old versions and clean up failed multipart uploads

Set up OCI Object Storage lifecycle rules with the CLI, add the service IAM policy they need, and avoid the replace-not-merge trap that silently drops existing rules.

Deepak Bhardwaj4 min read

Applies toOCI Object Storage (Standard, Infrequent Access and Archive tiers), OCI CLI 3.x

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

put replaces the whole policy. Any rule not in your file is removed. Always get first, merge the existing rules into your file, then put. 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 DELETE rules on a non-production bucket, or start with a long timeAmount and shorten it later.

Prevent it next time

  • Put an ABORT rule for multipart uploads on every bucket; failed uploads are invisible storage cost.
  • Turn on versioning only together with a previous-object-versions delete rule.
  • Manage lifecycle policies in Terraform (oci_objectstorage_object_lifecycle_policy) so a manual put can't silently drop rules.
  • Review tiering choices against current OCI storage pricing; Archive has a minimum retention period and restore delays.