Transitioning Media to Cheaper Storage Classes

Keep originals and derivatives in S3 Standard while they are being viewed, then let a lifecycle rule move originals — which are rarely read after processing — to Glacier Instant Retrieval after 30–90 days, and use Intelligent-Tiering for derivatives whose popularity you cannot predict. Filter rules by prefix or tag and by ObjectSizeGreaterThan of at least 128 KB, because small objects are billed as 128 KB in the infrequent-access classes and every transition is a paid request; and never transition objects you will delete within the class’s minimum storage duration, which is charged in full regardless.

Media libraries follow a steep access curve: an upload is viewed heavily for days, occasionally for weeks, and then almost never — but must stay available. Paying Standard prices for years of cold originals is the single largest avoidable cost in most upload-heavy products. This page belongs to cloud storage lifecycle rules in backend validation and cloud storage architecture. The equivalent rules for other providers are in lifecycle policies for GCS and Azure Blob; cleanup of temporary objects is in setting up S3 lifecycle rules for temporary uploads.

When to use this approach

  • You store more than a few terabytes of user media and it keeps growing.
  • Originals are kept for re-processing or download but served mostly via derivatives.
  • Access logs or S3 Storage Lens show most bytes untouched after the first month.

Prerequisites

  1. An S3 bucket with a key layout that separates originals and derivatives (originals/, derived/), or object tags that do.
  2. Permission to put a bucket lifecycle configuration (s3:PutLifecycleConfiguration).
  3. Some access data — S3 Storage Class Analysis on the prefix for 30 days is enough to decide.
  4. Terraform 1.5+ with the AWS provider 5.x, or the AWS CLI.

The classes that matter for media

S3 storage classes compared for media workloads Standard has no minimum duration or retrieval fee. Standard-IA and One Zone-IA have a 30 day minimum, a 128 KB minimum billable size and a per-gigabyte retrieval fee. Glacier Instant Retrieval has a 90 day minimum, millisecond access and a higher retrieval fee. Glacier Flexible and Deep Archive need restores taking minutes to hours. Intelligent-Tiering moves objects automatically for a small monitoring fee per object. Cheaper storage means paying on access instead class min. days first byte good for Standard ms new uploads, hot derivatives Standard-IA 30 ms monthly-ish access Glacier Instant Retrieval 90 ms cold originals, still downloadable Intelligent-Tiering ms unpredictable popularity Glacier Flexible Retrieval 90 minutes–hours archives users restore on request Glacier Deep Archive 180 up to 12 h compliance copies IA and Glacier IR bill objects under 128 KB as 128 KB; Glacier classes add about 40 KB of overhead per object.
For media that must stay one click away, Glacier Instant Retrieval is usually the sweet spot for originals.

Implementation

Tag originals at upload time (or rely on the prefix), then define the rules. Terraform:

resource "aws_s3_bucket_lifecycle_configuration" "media" {
  bucket = aws_s3_bucket.media.id

  rule {
    id     = "originals-to-glacier-ir"
    status = "Enabled"
    filter {
      and {
        prefix                   = "originals/"
        object_size_greater_than = 131072          # 128 KB: smaller objects cost more in IA/GIR
      }
    }
    transition {
      days          = 60
      storage_class = "GLACIER_IR"
    }
    noncurrent_version_transition {
      noncurrent_days = 30
      storage_class   = "GLACIER_IR"
    }
    noncurrent_version_expiration {
      noncurrent_days = 120                        # past the 90-day minimum, so no early-delete fee
    }
  }

  rule {
    id     = "derived-intelligent-tiering"
    status = "Enabled"
    filter {
      and {
        prefix                   = "derived/"
        object_size_greater_than = 131072          # IT does not monitor objects under 128 KB anyway
      }
    }
    transition {
      days          = 0                            # immediately; IT has no retrieval fees
      storage_class = "INTELLIGENT_TIERING"
    }
  }

  rule {
    id     = "abort-incomplete-multipart"
    status = "Enabled"
    filter {}
    abort_incomplete_multipart_upload { days_after_initiation = 3 }
  }
}

# Opt-in archive tiers inside Intelligent-Tiering for derivatives nobody has touched in months.
resource "aws_s3_bucket_intelligent_tiering_configuration" "derived" {
  bucket = aws_s3_bucket.media.id
  name   = "derived-archive"
  filter { prefix = "derived/" }
  tiering {
    access_tier = "ARCHIVE_ACCESS"
    days        = 180
  }
}

The break-even check, as a small script you run before enabling a rule:

// Prices in USD per GB-month / per 1,000 requests / per GB retrieved (us-east-1, check current pricing).
const P = { standard: 0.023, gir: 0.004, transitionPer1k: 0.02, girRetrievalPerGB: 0.03 };

export function monthlySavings(opts: { gb: number; objects: number; retrievedGBPerMonth: number; monthsKept: number }) {
  const storageSaved = opts.gb * (P.standard - P.gir);
  const retrievalCost = opts.retrievedGBPerMonth * P.girRetrievalPerGB;
  const transitionOnce = (opts.objects / 1000) * P.transitionPer1k;
  const net = storageSaved - retrievalCost - transitionOnce / opts.monthsKept;
  return { storageSaved, retrievalCost, transitionAmortised: transitionOnce / opts.monthsKept, net };
}

console.log(monthlySavings({ gb: 50_000, objects: 20_000_000, retrievedGBPerMonth: 800, monthsKept: 36 }));
// { storageSaved: 950, retrievalCost: 24, transitionAmortised: 11.1, net: 914.9 }  per month

Line-by-line on the decisions that matter

  • Originals and derivatives in separate rules. They have different access patterns. Originals are read when a user downloads “original quality” or you re-process; derivatives are what players and pages fetch. A single rule for both either leaves originals too warm or pushes derivatives somewhere expensive to read.
  • object_size_greater_than = 131072. IA and Glacier IR bill anything smaller as 128 KB, and each transition costs a request fee. Thumbnails and small derivatives cost more after transition than before. The filter keeps them in Standard.
  • 60 days for originals. Long enough that re-processing after an upload (a new rendition, a failed job retried) has finished, and past most of the access curve. Storage Class Analysis tells you the right number for your data.
  • Noncurrent versions. With versioning on, overwritten or deleted originals linger as noncurrent versions at full price. Transition and then expire them; expiring after 120 days avoids the early-deletion charge that Glacier IR’s 90-day minimum would otherwise add.
  • Intelligent-Tiering for derivatives. Some derivatives go viral a year later. IT moves objects between frequent and infrequent tiers automatically with no retrieval fee, for a small monitoring fee per object; that makes it safe for data you cannot predict.
  • Archive tier opt-in. IT’s archive tiers are cheap but require a restore before access. Only enable them where your application handles a restore delay — for derivatives you can regenerate, it is usually easier to delete and re-derive on demand.

How objects move over time

Timeline of an original and its derivatives through storage classes On day zero the original and derivatives are in Standard; derivatives move to Intelligent-Tiering immediately. After 30 days without access IT moves them to its infrequent tier. On day 60 the original moves to Glacier Instant Retrieval. After 180 days without access derivatives can move to IT's archive tier. Day 0 to day 180 for one upload day 0 30 60 180 original Standard Glacier Instant Retrieval derivatives IT frequent IT infrequent (back to frequent on access) Grey block: optional IT archive tier.
Originals follow a fixed schedule; derivatives follow their own access pattern.

Measuring before and after

Turn on S3 Storage Class Analysis for each prefix and let it run for at least 30 days before choosing transition ages; it reports how much data is accessed at each age and recommends when infrequent access begins. After enabling rules, watch three things in Cost Explorer grouped by usage type: storage by class (should shift), Requests-Tier and transition request charges (a one-off spike), and retrieval charges (should stay small). A retrieval line that grows month on month means a rule moved data that is still warm; lengthen the transition age.

Watch application latency too. Glacier IR and IA serve first bytes in milliseconds, but through a CDN any extra origin latency is hidden only for cached objects. If your app re-processes originals in bulk — a new rendition for the whole library — plan the retrieval cost and run it once, rather than letting a background job trickle through the archive for months.

Cost signals to watch after enabling transitions Storage cost by class should shift from Standard to cheaper classes. Transition request charges should spike once and then settle. Retrieval charges should stay small; a steadily rising retrieval line means a rule transitions data that is still being read. Three lines tell you if the rule is right storage by class Standard shrinks GIR / IT grow expected transition requests spike in month one then new uploads only expected retrieval fees should stay flat rising = data too warm lengthen the age Group Cost Explorer by usage type and filter to the bucket's tag to see these clearly.
Rising retrieval charges are the signal that a transition happens too early.

Designing keys and tags for tiering

Lifecycle filters can match a prefix, object tags and object size — nothing else. That makes key layout a cost decision made on the day you design uploads. Put the object’s role first in the key (originals/, derived/, temp/) rather than the tenant or date, so one rule per role covers the whole bucket. If you already key by tenant first (tenants/<id>/…), use object tags such as role=original set when the object is written; a presigned PUT can require the tag through the signed x-amz-tagging header, and server-side copies can set it directly.

Tags also let you tier by business value rather than age alone. A plan=free tag lets free-tier originals transition after 30 days while paid customers’ stay warm for 90, and a retain=legal tag can exclude held objects from expiration rules entirely. Keep the number of tag values small — each lifecycle configuration is limited to 1,000 rules, and every distinct combination you want to treat differently needs one.

Finally, write the reasoning down next to the rules. Transition ages chosen from a month of Storage Class Analysis look arbitrary a year later; a comment with the analysis date and the observed access curve saves the next person from undoing a good decision.

Configuration gotchas

Transition to IA or GIR fails silently for young objects. S3 requires objects to be at least 30 days old before moving to Standard-IA or One Zone-IA via lifecycle. Rules with days under 30 for those classes are rejected or never apply; Glacier IR and Intelligent-Tiering have no such floor.

Costs went up after enabling a rule. Usually small objects: millions of thumbnails moved and each now bills as 128 KB, plus transition requests. Add the size filter and let Intelligent-Tiering handle anything uncertain.

Deleting users’ data triggers early-deletion fees. Deleting a Glacier IR object before 90 days bills the remaining days. Account for it in your deletion policy, or transition later if many uploads are deleted in their first months.

Rules seem to do nothing for a day. Lifecycle rules run asynchronously, typically once a day; transitions for existing objects can take a day or two to appear.

Verification

aws s3api get-bucket-lifecycle-configuration --bucket media-prod | jq '.Rules[] | {ID, Status, Filter, Transitions}'
aws s3api head-object --bucket media-prod --key originals/2026/07/abc.mov --query StorageClass
# "GLACIER_IR"
aws s3api list-objects-v2 --bucket media-prod --prefix originals/2026/ --query 'Contents[?StorageClass!=`GLACIER_IR`]|length(@)'

Frequently Asked Questions

Should everything go into Intelligent-Tiering?

It is a reasonable default for data you cannot predict, but for originals with a known cold pattern, a fixed transition to Glacier IR is cheaper because IT’s infrequent tiers are priced above Glacier IR and the monitoring fee adds up for millions of objects.

Does transitioning affect presigned URLs or CDN origins?

No. Instant-access classes serve GET requests normally; only Glacier Flexible, Deep Archive and IT’s archive tiers need a restore first.

What about Glacier Deep Archive for originals?

Only if you can tell users that original downloads take up to a day and you have a restore workflow. For most products, the savings over Glacier IR are not worth that experience.