Archival Storage Best Practices
This is the checklist to run before and after moving a workload into Glacier or Deep Archive from the SDK.
Search across all documentation pages
This is the checklist to run before and after moving a workload into Glacier or Deep Archive from the SDK.
Each item is a rule stated positively, with a one-line rationale. Work top to bottom - the groups run from choosing the right approach, through retention and restore design, to cost control and operations. Most rules apply whether you call S3 from boto3, the AWS SDK v3, or IaC.
GLACIER_IR for instant reads, GLACIER for minutes-to-hours, DEEP_ARCHIVE for the cheapest, slowest cold storage.Choose the class deliberately - it dictates both cost and the code path to read.
# --- Python (boto3) ---
import boto3
s3 = boto3.client("s3")
# Born-cold data: write straight into DEEP_ARCHIVE, no lifecycle wait.
s3.put_object(Bucket="my-bucket", Key="archive/2018-backup.tar", Body=b"...", StorageClass="DEEP_ARCHIVE")// --- TypeScript (AWS SDK v3) ---
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
const s3 = new S3Client({});
// Born-cold data: write straight into DEEP_ARCHIVE, no lifecycle wait.
await s3.send(new PutObjectCommand({
Bucket: "my-bucket", Key: "archive/2018-backup.tar", Body: Buffer.from("..."), StorageClass: "DEEP_ARCHIVE",
}));PutBucketLifecycleConfiguration cool warm data into archival classes by age instead of re-tiering by hand.PutBucketLifecycleConfiguration replaces the whole configuration - read, edit, and write the complete set to avoid wiping rules.Expiration so retention ends and storage stops billing when data is no longer required.restore_object starts a job; the data is ready minutes to hours later, so never block a synchronous request on it.s3:ObjectRestore:Completed instead of looping on head_object, which is cheaper and faster.Days; size it to your processing window so you do not re-restore.RestoreAlreadyInProgress - inspect state first.Wait on an event rather than a poll loop, and check state before restoring again.
# --- Python (boto3) ---
import boto3
s3 = boto3.client("s3")
head = s3.head_object(Bucket="my-bucket", Key="archive/report.pdf")
restore = head.get("Restore", "")
if not restore: # no restore yet - request one
s3.restore_object(
Bucket="my-bucket", Key="archive/report.pdf",
RestoreRequest={"Days": 2, "GlacierJobParameters": {"Tier": "Bulk"}},
)// --- TypeScript (AWS SDK v3) ---
import { S3Client, HeadObjectCommand, RestoreObjectCommand } from "@aws-sdk/client-s3";
const s3 = new S3Client({});
const head = await s3.send(new HeadObjectCommand({ Bucket: "my-bucket", Key: "archive/report.pdf" }));
if (!head.Restore) { // no restore yet - request one
await s3.send(new RestoreObjectCommand({
Bucket: "my-bucket", Key: "archive/report.pdf",
RestoreRequest: { Days: 2, GlacierJobParameters: { Tier: "Bulk" } },
}));
}GLACIER if you need Expedited.InsufficientCapacityException and do not need it.aws:SecureTransport bucket policy.s3:RestoreObject and s3:GetObject to the specific archival prefix, not s3:* on *.RequestId/$metadata.requestId so AWS support can trace a specific call.GLACIER/DEEP_ARCHIVE objects to gain keys, lifecycle, and events.Use the S3 Glacier storage classes for all new work. They share S3's bucket, key, lifecycle, and event model. The standalone Vault service is legacy - keep it only for maintaining or migrating existing deployments.
Match the class to how fast you must read data back: GLACIER_IR for instant millisecond reads, GLACIER when minutes-to-hours is acceptable, and DEEP_ARCHIVE for the cheapest storage when hours-to-days retrieval is fine.
Archiving data you actually read often, or deleting it before the minimum storage duration. Retrieval fees and early-delete charges can exceed S3 Standard. Estimate reads per object per year, and respect the 90-day and 180-day minimums.
Asynchronously. Call restore_object, then react to an s3:ObjectRestore:Completed event rather than polling. Default background restores to the Bulk tier, and reserve Expedited for objects a user is waiting on.
Bulk has the lowest per-gigabyte retrieval cost. For large, non-urgent restores you can run overnight, it dramatically cuts the retrieval bill compared with Standard or Expedited, at the cost of longer latency you do not care about for batch work.
Set the Days window on the restore to cover your whole processing window, and check the Restore header before issuing another restore. A second restore while one is in progress raises RestoreAlreadyInProgress.
Mostly yes. Storage-class choice, lifecycle rules, expiration, encryption, least-privilege policies, versioning, and tagging are architectural habits that apply whether you configure S3 from the SDK or from CloudFormation, CDK, or Terraform.
Use S3 Object Lock with a retention period for WORM (write-once-read-many) protection, keep the bucket private with Block Public Access, and enable versioning. Together they prevent accidental or malicious overwrite and delete during the retention window.
The archive-id-to-meaning mapping, in your own store. Vault archives have no keys - only opaque archive ids returned at upload - and the vault inventory lags by about a day, so losing your own records can strand the data.
Estimate reads per year before archiving, respect minimum durations, default restores to Bulk, expire data when retention ends, keep tiny objects out of cold classes, and tag objects so you can attribute and monitor cost.
Stack versions: This page was written for boto3 1.43.x (Python 3.10+) and the AWS SDK for JavaScript v3 (Node.js 18+).
Reviewed by Chris St. John·Last updated Jul 23, 2026