AWS for DevOps — S3 Versioning & Lifecycle
5.2 — S3 Versioning & Lifecycle
Versioning protects against accidental deletes. Lifecycle policies automatically move or expire objects to save costs.
Versioning
When versioning is enabled, S3 keeps every version of every object:
s3://my-bucket/config.yaml
├── Version: v3 (current) ← what you see by default
├── Version: v2
└── Version: v1
# Enable versioning
$ aws s3api put-bucket-versioning \
--bucket my-devops-artefacts-2026 \
--versioning-configuration Status=Enabled
# Upload a file (creates version 1)
$ aws s3 cp config.yaml s3://my-devops-artefacts-2026/config.yaml
# Upload again (creates version 2 — version 1 is preserved)
$ aws s3 cp config-updated.yaml s3://my-devops-artefacts-2026/config.yaml
# List all versions
$ aws s3api list-object-versions \
--bucket my-devops-artefacts-2026 \
--prefix config.yaml \
--query 'Versions[*].[Key,VersionId,IsLatest,LastModified]' \
--output table
---------------------------------------------------------------
| config.yaml | abc123 | True | 2026-03-30T10:30:00Z |
| config.yaml | def456 | False | 2026-03-30T10:00:00Z |
---------------------------------------------------------------
# Retrieve a specific version
$ aws s3api get-object \
--bucket my-devops-artefacts-2026 \
--key config.yaml \
--version-id def456 \
config-v1.yaml
How Deletes Work with Versioning
When you “delete” a versioned object, S3 adds a delete marker — the previous versions still exist:
# "Delete" the object
$ aws s3 rm s3://my-devops-artefacts-2026/config.yaml
# The object appears deleted, but versions are still there
$ aws s3api list-object-versions \
--bucket my-devops-artefacts-2026 \
--prefix config.yaml
# Shows: DeleteMarker (latest) + all previous versions
# Restore by deleting the delete marker
$ aws s3api delete-object \
--bucket my-devops-artefacts-2026 \
--key config.yaml \
--version-id <delete-marker-version-id>
Lifecycle Policies
Lifecycle rules automatically transition or expire objects:
$ aws s3api put-bucket-lifecycle-configuration \
--bucket my-devops-artefacts-2026 \
--lifecycle-configuration '{
"Rules": [
{
"ID": "ArchiveOldBuilds",
"Status": "Enabled",
"Filter": {"Prefix": "builds/"},
"Transitions": [
{
"Days": 30,
"StorageClass": "STANDARD_IA"
},
{
"Days": 90,
"StorageClass": "GLACIER_IR"
}
],
"Expiration": {
"Days": 365
}
},
{
"ID": "CleanupOldVersions",
"Status": "Enabled",
"Filter": {"Prefix": ""},
"NoncurrentVersionExpiration": {
"NoncurrentDays": 30
}
}
]
}'
This does:
- After 30 days → move builds to Standard-IA (cheaper)
- After 90 days → move to Glacier Instant Retrieval (even cheaper)
- After 365 days → delete permanently
- Old versions of ALL objects → delete after 30 days
Lifecycle Visualisation
Day 0 Day 30 Day 90 Day 365
──────────────────────────────────────────────────────────►
│ Standard │──► Standard-IA │──► Glacier IR │──► Deleted
│ $0.023/GB│ $0.0125/GB │ $0.004/GB │
Common Lifecycle Patterns for DevOps
| Object Type | Keep in Standard | Move to IA | Archive/Delete |
|---|---|---|---|
| Build artefacts | 7 days | 30 days | Delete at 90 days |
| Application logs | 14 days | 60 days | Delete at 180 days |
| Terraform state | Always | Never | Never (versioned) |
| Database backups | 7 days | 30 days | Glacier at 90, delete at 365 |
| CloudTrail logs | 90 days | 180 days | Glacier at 365 |
Quick Reference
| Task | Command |
|---|---|
| Enable versioning | aws s3api put-bucket-versioning --bucket BUCKET --versioning-configuration Status=Enabled |
| List versions | aws s3api list-object-versions --bucket BUCKET --prefix KEY |
| Get specific version | aws s3api get-object --bucket BUCKET --key KEY --version-id VID FILE |
| Set lifecycle | aws s3api put-bucket-lifecycle-configuration --bucket BUCKET --lifecycle-configuration file://lifecycle.json |
| Get lifecycle | aws s3api get-bucket-lifecycle-configuration --bucket BUCKET |
Next → S3 for DevOps Workflows