ποΈ Amazon S3 Versioning
Safely update files without ever losing older copies. Once enabled, every new upload gets its own version automatically β essential for static websites, backups, and critical files.
β How It Works
<div class="infographic-s3ver">
<style>
.infographic-s3ver {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f6f4ef;
--accent: #2563eb;
--accent-soft:#e3ebfd;
--line: #ddd6c9;
--bucket: #1f7a4d;
--user: #333333;
--v1: #c9781f;
--v2: #1f8a4c;
--v3: #7a3fd6;
--v4: #1f6fd1;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
color: var(--ink);
background: var(--paper);
max-width: 460px;
margin: 0 auto;
padding: 1.5rem 1.25rem 2rem;
box-sizing: border-box;
}
.infographic-s3ver[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #121212;
--paper-alt: #1e1e26;
--accent: #6f9bff;
--accent-soft:#1c2740;
--line: #2c313d;
--bucket: #4fbf85;
--user: #d8d5cd;
--v1: #e0a052;
--v2: #59c07f;
--v3: #a586f0;
--v4: #6ea3f5;
}
.infographic-s3ver * { box-sizing: border-box; }
.s3v-canvas { width: 100%; height: auto; display: block; overflow: visible; }
.s3v-label { font-size: 12px; font-weight: 700; fill: var(--ink); }
.s3v-sublabel { font-size: 10px; fill: var(--ink-soft); }
.s3v-path { font-family: 'SF Mono', Consolas, monospace; font-size: 9.5px; fill: var(--ink-soft); }
.s3v-vlabel { font-size: 11px; font-weight: 700; }
.s3v-person { fill: none; stroke: var(--user); stroke-width: 2.2; stroke-linejoin: round; }
.s3v-connector { stroke: var(--ink-soft); stroke-width: 1.5; stroke-dasharray: 3 3; }
.s3v-doc { fill: var(--paper); stroke: var(--ink); stroke-width: 2; stroke-linejoin: round; transition: transform 0.5s cubic-bezier(.4,0,.2,1), opacity 0.35s ease; }
.s3v-doc-lines { stroke: var(--ink-soft); stroke-width: 1.3; }
.s3v-bucketbox { fill: none; stroke: var(--bucket); stroke-width: 2; stroke-dasharray: 5 4; }
.s3v-bucketicon { fill: none; stroke: var(--bucket); stroke-width: 2.4; stroke-linejoin: round; }
.s3v-card {
fill: var(--paper); stroke-width: 2.2; stroke-linejoin: round;
opacity: 0; transform: scale(0.5); transform-origin: center;
transition: opacity 0.4s ease, transform 0.4s cubic-bezier(.34,1.56,.64,1);
}
.s3v-card.is-visible { opacity: 1; transform: scale(1); }
.s3v-card-lines { stroke-width: 1.3; opacity: 0; transition: opacity 0.4s ease 0.1s; }
.s3v-card-lines.is-visible { opacity: 1; }
.s3v-vtext { opacity: 0; transition: opacity 0.3s ease 0.15s; }
.s3v-vtext.is-visible { opacity: 1; }
.s3v-controls { display: flex; justify-content: center; gap: 0.6rem; margin-top: 1.25rem; }
.s3v-btn {
font: inherit; font-size: 0.85rem; font-weight: 600; color: var(--ink);
background: var(--paper-alt); border: 1.5px solid var(--line); border-radius: 999px;
padding: 0.5rem 1.1rem; cursor: pointer; transition: border-color 0.15s, background 0.15s;
}
.s3v-btn:hover { border-color: var(--accent); }
.s3v-btn:disabled { opacity: 0.4; cursor: default; }
.s3v-btn.upload { color: #fff; border-color: var(--accent); background: var(--accent); }
.s3v-btn.reset { color: var(--accent); border-color: var(--accent); background: var(--accent-soft); }
@media (prefers-reduced-motion: reduce) {
.infographic-s3ver * { transition-duration: 0.001ms !important; }
}
</style>
<svg class="s3v-canvas" viewBox="0 0 300 430" xmlns="http://www.w3.org/2000/svg">
<!-- user -->
<g transform="translate(150,28)">
<circle class="s3v-person" cx="0" cy="0" r="9"/>
<path class="s3v-person" d="M-14,26 A14,16 0 0 1 14,26"/>
</g>
<text class="s3v-label" x="150" y="74" text-anchor="middle">User</text>
<!-- connector -->
<line class="s3v-connector" x1="150" y1="80" x2="150" y2="112"/>
<text class="s3v-sublabel" x="100" y="99" text-anchor="middle">upload</text>
<!-- falling doc -->
<g id="s3v-doc" transform="translate(150,110)">
<path class="s3v-doc" d="M-11,-14 L5,-14 L11,-8 L11,14 L-11,14 Z"/>
<path class="s3v-doc-lines" d="M-6,-3 H6 M-6,3 H6 M-6,9 H2"/>
</g>
<!-- bucket box -->
<rect class="s3v-bucketbox" x="20" y="140" width="260" height="255" rx="6"/>
<g transform="translate(38,150)">
<path class="s3v-bucketicon" d="M2,4 L6,26 L20,26 L24,4 Z"/>
<path class="s3v-bucketicon" d="M0,4 Q13,10 26,4"/>
</g>
<text class="s3v-label" x="75" y="163" text-anchor="start">S3 Bucket</text>
<text class="s3v-sublabel" x="75" y="177" text-anchor="start">(my-bucket)</text>
<!-- version card stack (fan out from base position) -->
<g id="s3v-stack" transform="translate(150,300)">
<g class="s3v-cardgroup" data-idx="0" transform="translate(-18,10)">
<path class="s3v-card" style="stroke:var(--v1)" d="M-20,-26 L2,-26 L14,-14 L14,26 L-20,26 Z"/>
<path class="s3v-card-lines" style="stroke:var(--v1)" d="M-12,-6 H6 M-12,2 H6 M-12,10 H0"/>
</g>
<g class="s3v-cardgroup" data-idx="1" transform="translate(0,-4)">
<path class="s3v-card" style="stroke:var(--v2)" d="M-20,-26 L2,-26 L14,-14 L14,26 L-20,26 Z"/>
<path class="s3v-card-lines" style="stroke:var(--v2)" d="M-12,-6 H6 M-12,2 H6 M-12,10 H0"/>
</g>
<g class="s3v-cardgroup" data-idx="2" transform="translate(18,-18)">
<path class="s3v-card" style="stroke:var(--v3)" d="M-20,-26 L2,-26 L14,-14 L14,26 L-20,26 Z"/>
<path class="s3v-card-lines" style="stroke:var(--v3)" d="M-12,-6 H6 M-12,2 H6 M-12,10 H0"/>
</g>
<g class="s3v-cardgroup" data-idx="3" transform="translate(36,-32)">
<path class="s3v-card" style="stroke:var(--v4)" d="M-20,-26 L2,-26 L14,-14 L14,26 L-20,26 Z"/>
<path class="s3v-card-lines" style="stroke:var(--v4)" d="M-12,-6 H6 M-12,2 H6 M-12,10 H0"/>
</g>
</g>
<!-- version legend (kept clear of the card fan and the bucket title) -->
<g id="s3v-legend">
<text class="s3v-vlabel s3v-vtext" data-idx="0" x="205" y="195" fill="var(--v1)">Version 1</text>
<text class="s3v-vlabel s3v-vtext" data-idx="1" x="205" y="215" fill="var(--v2)">Version 2</text>
<text class="s3v-vlabel s3v-vtext" data-idx="2" x="205" y="235" fill="var(--v3)">Version 3</text>
<text class="s3v-vlabel s3v-vtext" data-idx="3" x="205" y="255" fill="var(--v4)">Version 4</text>
</g>
<text class="s3v-path" x="150" y="378" text-anchor="middle">s3://my-bucket/my-file.docx</text>
</svg>
<div class="s3v-controls">
<button class="s3v-btn upload" id="s3v-upload">β¬ Upload</button>
<button class="s3v-btn reset" id="s3v-reset" disabled>βΊ Reset</button>
</div>
<script>
(function () {
const wrapper = document.currentScript.closest('.infographic-s3ver')
|| document.querySelector('.infographic-s3ver');
const bodyAttr = document.body.getAttribute('data-darkmode');
let dark;
if (bodyAttr === 'true') { dark = true; }
else if (bodyAttr === 'false') { dark = false; }
else {
const match = document.cookie.match(/(?:^|;\s*)darkMode=([^;]+)/);
dark = match ? match[1] === 'true' : false;
}
if (dark) wrapper.setAttribute('data-theme', 'dark');
const doc = wrapper.querySelector('#s3v-doc');
const cardGroups = wrapper.querySelectorAll('.s3v-cardgroup');
const legendLabels = wrapper.querySelectorAll('.s3v-vtext');
const uploadBtn = wrapper.querySelector('#s3v-upload');
const resetBtn = wrapper.querySelector('#s3v-reset');
const maxVersions = cardGroups.length;
let version = 0;
function upload() {
if (version >= maxVersions) return;
uploadBtn.disabled = true;
doc.style.transform = 'translate(0px,150px) scale(0.7)';
doc.style.opacity = '0';
setTimeout(function () {
cardGroups[version].querySelector('.s3v-card').classList.add('is-visible');
cardGroups[version].querySelector('.s3v-card-lines').classList.add('is-visible');
legendLabels[version].classList.add('is-visible');
version += 1;
resetBtn.disabled = false;
setTimeout(function () {
doc.style.transform = '';
doc.style.opacity = '1';
if (version < maxVersions) uploadBtn.disabled = false;
}, 80);
}, 480);
}
function reset() {
cardGroups.forEach(function (g) {
g.querySelector('.s3v-card').classList.remove('is-visible');
g.querySelector('.s3v-card-lines').classList.remove('is-visible');
});
legendLabels.forEach(function (l) { l.classList.remove('is-visible'); });
doc.style.transform = '';
doc.style.opacity = '1';
version = 0;
uploadBtn.disabled = false;
resetBtn.disabled = true;
}
uploadBtn.addEventListener('click', upload);
resetBtn.addEventListener('click', reset);
})();
</script>
</div>Every file in a bucket has a key
First upload β Version 1
Re-upload the same key β Version 2, 3β¦ π’
All previous versions stay safely retained π‘οΈ
π€ Benefits
β Protects against accidental deletes β deleting just adds a delete marker; old versions stay intact
βͺ Easy rollback β restore any previous version whenever needed
β οΈ Safe suspension β suspending versioning doesn't delete existing versions
π’ Files uploaded before versioning was enabled show version null
π Example
<div class="infographic-s3rollback">
<style>
.infographic-s3rollback {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f6f4ef;
--accent: #2563eb;
--accent-soft:#e3ebfd;
--line: #ddd6c9;
--v1: #c9781f;
--v2: #1f8a4c;
--serve: #1f8a4c;
--serve-soft: #e2f6ea;
--dead: #c0392b;
--dead-soft: #fbe9e7;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
color: var(--ink);
background: var(--paper);
max-width: 460px;
margin: 0 auto;
padding: 1.5rem 1.25rem 2rem;
box-sizing: border-box;
}
.infographic-s3rollback[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #121212;
--paper-alt: #1e1e26;
--accent: #6f9bff;
--accent-soft:#1c2740;
--line: #2c313d;
--v1: #e0a052;
--v2: #59c07f;
--serve: #59c07f;
--serve-soft: #122a1c;
--dead: #e57373;
--dead-soft: #3a2222;
}
.infographic-s3rollback * { box-sizing: border-box; }
.s3r-canvas {
width: 100%;
height: auto;
aspect-ratio: 320 / 230;
display: block;
overflow: visible;
}
.s3r-key { font-family: 'SF Mono', Consolas, monospace; font-size: 15px; font-weight: 700; fill: var(--ink); }
.s3r-connector { stroke: var(--ink-soft); stroke-width: 1.5; stroke-dasharray: 3 3; }
.s3r-card {
opacity: 0;
transform: translate(var(--cx), var(--cy)) scale(0.55);
transform-origin: center;
transition: opacity 0.4s ease, transform 0.4s cubic-bezier(.34,1.56,.64,1);
}
.s3r-card.is-created { opacity: 1; transform: translate(var(--cx), var(--cy)) scale(1); }
.s3r-card.is-dimmed { opacity: 0.4; }
.s3r-card-body { fill: var(--paper); stroke-width: 2.4; stroke-linejoin: round; transition: stroke 0.3s ease; }
.s3r-card-lines { stroke-width: 1.4; transition: stroke 0.3s ease; }
.s3r-strike { stroke-width: 3; stroke-linecap: round; opacity: 0; transition: opacity 0.3s ease; }
.s3r-strike.is-visible { opacity: 1; }
.s3r-vlabel { font-size: 14px; font-weight: 700; text-anchor: middle; transition: fill 0.3s ease; }
.s3r-vlabel.is-strike { text-decoration: line-through; }
.s3r-serving { opacity: 0; transition: opacity 0.3s ease 0.1s; }
.s3r-serving.is-visible { opacity: 1; }
.s3r-serving-pill { fill: var(--serve-soft); stroke: var(--serve); stroke-width: 1.5; }
.s3r-serving-text { font-size: 11px; font-weight: 700; fill: var(--serve); text-anchor: middle; }
.s3r-controls { display: flex; justify-content: center; gap: 0.5rem; margin-top: 1.5rem; flex-wrap: wrap; }
.s3r-btn {
font: inherit; font-size: 0.82rem; font-weight: 600; color: var(--ink);
background: var(--paper-alt); border: 1.5px solid var(--line); border-radius: 999px;
padding: 0.5rem 1rem; cursor: pointer; transition: border-color 0.15s, background 0.15s, opacity 0.15s;
}
.s3r-btn:hover:not(:disabled) { border-color: var(--accent); }
.s3r-btn:disabled { opacity: 0.35; cursor: default; }
.s3r-btn.primary { color: #fff; border-color: var(--accent); background: var(--accent); }
.s3r-btn.reset { color: var(--accent); border-color: var(--accent); background: var(--accent-soft); }
@media (prefers-reduced-motion: reduce) {
.infographic-s3rollback * { transition-duration: 0.001ms !important; }
}
</style>
<svg class="s3r-canvas" viewBox="0 0 320 230" width="320" height="230" preserveAspectRatio="xMidYMid meet" xmlns="http://www.w3.org/2000/svg">
<text class="s3r-key" x="160" y="24" text-anchor="middle">index.html</text>
<line class="s3r-connector" x1="160" y1="32" x2="160" y2="52"/>
<!-- Version 1 card -->
<g class="s3r-card" id="s3r-card-v1" style="--cx:120px; --cy:110px;">
<path class="s3r-card-body" id="s3r-body-v1" d="M-22,-30 L4,-30 L17,-17 L17,30 L-22,30 Z" style="stroke:var(--v1)"/>
<path class="s3r-card-lines" id="s3r-lines-v1" d="M-13,-6 H8 M-13,4 H8 M-13,14 H0" style="stroke:var(--v1)"/>
<line class="s3r-strike" id="s3r-strike-v1" x1="-24" y1="-22" x2="19" y2="22" style="stroke:var(--dead)"/>
<text class="s3r-vlabel" id="s3r-label-v1" x="-2" y="56" fill="var(--v1)">Version 1</text>
<g class="s3r-serving" id="s3r-serving-v1" transform="translate(-2,74)">
<rect class="s3r-serving-pill" x="-38" y="-11" width="76" height="20" rx="10"/>
<text class="s3r-serving-text" y="3">π’ Serving</text>
</g>
</g>
<!-- Version 2 card -->
<g class="s3r-card" id="s3r-card-v2" style="--cx:200px; --cy:110px;">
<path class="s3r-card-body" id="s3r-body-v2" d="M-22,-30 L4,-30 L17,-17 L17,30 L-22,30 Z" style="stroke:var(--v2)"/>
<path class="s3r-card-lines" id="s3r-lines-v2" d="M-13,-6 H8 M-13,4 H8 M-13,14 H0" style="stroke:var(--v2)"/>
<line class="s3r-strike" id="s3r-strike-v2" x1="-24" y1="-22" x2="19" y2="22" style="stroke:var(--dead)"/>
<text class="s3r-vlabel" id="s3r-label-v2" x="-2" y="56" fill="var(--v2)">Version 2</text>
<g class="s3r-serving" id="s3r-serving-v2" transform="translate(-2,74)">
<rect class="s3r-serving-pill" x="-38" y="-11" width="76" height="20" rx="10"/>
<text class="s3r-serving-text" y="3">π’ Serving</text>
</g>
</g>
</svg>
<div class="s3r-controls">
<button class="s3r-btn primary" id="s3r-upload">π Upload</button>
<button class="s3r-btn primary" id="s3r-update" disabled>βοΈ Update</button>
<button class="s3r-btn primary" id="s3r-rollback" disabled>π Rollback</button>
<button class="s3r-btn reset" id="s3r-reset" disabled>βΊ Reset</button>
</div>
<script>
(function () {
const wrapper = document.currentScript.closest('.infographic-s3rollback')
|| document.querySelector('.infographic-s3rollback');
const bodyAttr = document.body.getAttribute('data-darkmode');
let dark;
if (bodyAttr === 'true') { dark = true; }
else if (bodyAttr === 'false') { dark = false; }
else {
const match = document.cookie.match(/(?:^|;\s*)darkMode=([^;]+)/);
dark = match ? match[1] === 'true' : false;
}
if (dark) wrapper.setAttribute('data-theme', 'dark');
const cardV1 = wrapper.querySelector('#s3r-card-v1');
const cardV2 = wrapper.querySelector('#s3r-card-v2');
const bodyV1 = wrapper.querySelector('#s3r-body-v1');
const linesV1 = wrapper.querySelector('#s3r-lines-v1');
const labelV1 = wrapper.querySelector('#s3r-label-v1');
const strikeV1 = wrapper.querySelector('#s3r-strike-v1');
const servingV1 = wrapper.querySelector('#s3r-serving-v1');
const bodyV2 = wrapper.querySelector('#s3r-body-v2');
const linesV2 = wrapper.querySelector('#s3r-lines-v2');
const labelV2 = wrapper.querySelector('#s3r-label-v2');
const strikeV2 = wrapper.querySelector('#s3r-strike-v2');
const servingV2 = wrapper.querySelector('#s3r-serving-v2');
const uploadBtn = wrapper.querySelector('#s3r-upload');
const updateBtn = wrapper.querySelector('#s3r-update');
const rollbackBtn = wrapper.querySelector('#s3r-rollback');
const resetBtn = wrapper.querySelector('#s3r-reset');
let step = 0; // 0 none, 1 uploaded, 2 updated, 3 rolled back
function setColor(bodyEl, linesEl, labelEl, colorVar) {
bodyEl.style.stroke = colorVar;
linesEl.style.stroke = colorVar;
labelEl.style.fill = colorVar;
}
function render() {
cardV1.classList.toggle('is-created', step >= 1);
cardV2.classList.toggle('is-created', step >= 2);
cardV1.classList.toggle('is-dimmed', step === 2);
cardV2.classList.toggle('is-dimmed', false);
servingV1.classList.toggle('is-visible', step === 1 || step === 3);
servingV2.classList.toggle('is-visible', step === 2);
strikeV1.classList.remove('is-visible');
strikeV2.classList.toggle('is-visible', step === 3);
labelV2.classList.toggle('is-strike', step === 3);
if (step === 3) {
setColor(bodyV1, linesV1, labelV1, 'var(--serve)');
setColor(bodyV2, linesV2, labelV2, 'var(--dead)');
} else {
setColor(bodyV1, linesV1, labelV1, 'var(--v1)');
setColor(bodyV2, linesV2, labelV2, 'var(--v2)');
}
uploadBtn.disabled = step >= 1;
updateBtn.disabled = step !== 1;
rollbackBtn.disabled = step !== 2;
resetBtn.disabled = step === 0;
}
uploadBtn.addEventListener('click', function () { step = 1; render(); });
updateBtn.addEventListener('click', function () { step = 2; render(); });
rollbackBtn.addEventListener('click', function () { step = 3; render(); });
resetBtn.addEventListener('click', function () { step = 0; render(); });
render();
})();
</script>
</div>Upload
index.htmlπ β Version 1Update
index.htmlβοΈ β Version 2Rollback β Serve Version 1 π
β Best Practice
Versioning is essential for production buckets β it guarantees safety and quick recoverability.