Amazon S3 for Developers
A comprehensive deep dive into Amazon S3 for application developers — consistency model, storage classes, versioning, multipart upload, pre-signed URLs, event notifications, encryption options, access control, lifecycle policies, replication, and DVA-C02 exam essentials.
What is Amazon S3?
Amazon S3 (Simple Storage Service) is an object store — not a file system or block device. Objects are stored in buckets, retrieved by key, and each object can be up to 5 TB. S3 is designed for 11 nines (99.999999999%) of durability by replicating data across multiple AZs.
Core mental model: S3 is a flat key-value store. The "folders" you see in the console are just key prefixes (e.g.,
images/profile/alice.jpg— the full key). There is no real directory hierarchy.
Consistency Model
Since December 2020, S3 provides strong read-after-write consistency for all operations:
| Operation | Before Dec 2020 | Now |
|---|---|---|
| PUT new object → GET | Eventual (possible stale 404) | Strongly consistent |
| PUT overwrite → GET | Eventual | Strongly consistent |
| DELETE → GET | Eventual | Strongly consistent |
| LIST after PUT | Eventual | Strongly consistent |
No extra configuration needed — strong consistency is the default for all buckets.
Storage Classes
| Class | Availability | Min storage | Min duration | Use case |
|---|---|---|---|---|
| S3 Standard | 99.99% | None | None | Frequently accessed data |
| S3 Intelligent-Tiering | 99.9% | None | None | Unknown/changing access patterns |
| S3 Standard-IA | 99.9% | 128 KB | 30 days | Infrequently accessed, rapid retrieval |
| S3 One Zone-IA | 99.5% | 128 KB | 30 days | IA data, loss-tolerant (single AZ) |
| S3 Glacier Instant | 99.9% | 128 KB | 90 days | Archive, millisecond retrieval |
| S3 Glacier Flexible | 99.99% | 40 KB | 90 days | Archive, minutes-to-hours retrieval |
| S3 Glacier Deep Archive | 99.99% | 40 KB | 180 days | Long-term archive, 12h retrieval |
| S3 Express One Zone | 99.95% | None | None | Ultra-low latency (single AZ, new) |
All classes except One Zone-IA and Express One Zone replicate across ≥ 3 AZs.
Object Operations
Basic CRUD
1import {
2 S3Client, PutObjectCommand, GetObjectCommand,
3 DeleteObjectCommand, CopyObjectCommand, HeadObjectCommand,
4 ListObjectsV2Command,
5} from '@aws-sdk/client-s3';
6
7const s3 = new S3Client({ region: 'us-east-1' });
8
9// ── PUT ───────────────────────────────────────────────────────────────────────
10await s3.send(new PutObjectCommand({
11 Bucket: 'my-bucket',
12 Key: 'uploads/profile/alice.jpg',
13 Body: fileBuffer,
14 ContentType: 'image/jpeg',
15 Metadata: { 'uploaded-by': 'alice', 'source': 'mobile-app' },
16 StorageClass: 'STANDARD_IA',
17 ServerSideEncryption: 'aws:kms',
18 SSEKMSKeyId: 'alias/my-key',
19 Tagging: 'env=prod&team=platform',
20}));
21
22// ── GET ───────────────────────────────────────────────────────────────────────
23const { Body, ContentType, ContentLength } = await s3.send(new GetObjectCommand({
24 Bucket: 'my-bucket',
25 Key: 'uploads/profile/alice.jpg',
26 // Range: 'bytes=0-1023', // byte-range fetch (first 1 KB)
27}));
28const buffer = Buffer.from(await Body.transformToByteArray());
29
30// ── HEAD (metadata without body) ─────────────────────────────────────────────
31const { ContentLength: size, LastModified, Metadata } = await s3.send(
32 new HeadObjectCommand({ Bucket: 'my-bucket', Key: 'uploads/profile/alice.jpg' })
33);
34
35// ── COPY ─────────────────────────────────────────────────────────────────────
36await s3.send(new CopyObjectCommand({
37 CopySource: 'source-bucket/original/file.txt',
38 Bucket: 'dest-bucket',
39 Key: 'copies/file.txt',
40 StorageClass: 'GLACIER',
41}));
42
43// ── LIST (paginated) ─────────────────────────────────────────────────────────
44let continuationToken;
45do {
46 const { Contents, NextContinuationToken } = await s3.send(
47 new ListObjectsV2Command({
48 Bucket: 'my-bucket',
49 Prefix: 'uploads/',
50 MaxKeys: 1000,
51 ContinuationToken: continuationToken,
52 })
53 );
54 for (const obj of Contents ?? []) {
55 console.log(obj.Key, obj.Size, obj.LastModified);
56 }
57 continuationToken = NextContinuationToken;
58} while (continuationToken);Multipart Upload
Multipart upload splits large files into parts that upload in parallel:
| Rule | Value |
|---|---|
| Recommended for | Objects > 100 MB |
| Required for | Objects > 5 GB |
| Max object size | 5 TB |
| Min part size | 5 MB (except the last part) |
| Max parts | 10,000 |
| Incomplete uploads | Must call AbortMultipartUpload or use lifecycle rule to auto-clean |
1import { Upload } from '@aws-sdk/lib-storage';
2import { createReadStream } from 'fs';
3
4// High-level Upload class handles multipart automatically
5const upload = new Upload({
6 client: s3,
7 params: {
8 Bucket: 'my-bucket',
9 Key: 'large-file.zip',
10 Body: createReadStream('/tmp/large-file.zip'),
11 ContentType: 'application/zip',
12 },
13 partSize: 10 * 1024 * 1024, // 10 MB per part
14 queueSize: 4, // 4 concurrent part uploads
15});
16
17upload.on('httpUploadProgress', ({ loaded, total }) => {
18 console.log(`Uploaded ${loaded} / ${total} bytes`);
19});
20
21await upload.done();Pre-signed URLs
Pre-signed URLs grant temporary access to a specific S3 object without requiring AWS credentials on the client.
1import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
2
3// ── PRE-SIGNED GET (download) ─────────────────────────────────────────────────
4const downloadUrl = await getSignedUrl(s3, new GetObjectCommand({
5 Bucket: 'my-bucket',
6 Key: 'reports/invoice-2024-01.pdf',
7 ResponseContentDisposition: 'attachment; filename="invoice.pdf"',
8}), { expiresIn: 3600 }); // 1 hour
9
10// ── PRE-SIGNED PUT (direct browser upload) ────────────────────────────────────
11const uploadUrl = await getSignedUrl(s3, new PutObjectCommand({
12 Bucket: 'my-bucket',
13 Key: `uploads/${userId}/${filename}`,
14 ContentType: 'image/jpeg',
15}), { expiresIn: 300 }); // 5 minutes
16
17// Client uses uploadUrl with a plain HTTP PUT — no AWS credentials needed
18// fetch(uploadUrl, { method: 'PUT', body: file, headers: { 'Content-Type': 'image/jpeg' } })| Signed by | Max expiry |
|---|---|
| IAM user (long-term credentials) | 7 days |
| IAM role / STS credentials | 12 hours (capped by STS session duration) |
| EC2 Instance Profile | 6 hours |
Gotcha: Pre-signed URLs expire when the signing credentials expire — even if the URL's own expiry hasn't been reached. An instance profile token that expires in 1 hour will invalidate a pre-signed URL that was set to 6 hours.
S3 Event Notifications
S3 can trigger actions when objects are created, deleted, restored, or replicated.
1// Lambda triggered by S3 ObjectCreated event
2export async function handler(event) {
3 for (const record of event.Records) {
4 const bucket = record.s3.bucket.name;
5 const key = decodeURIComponent(record.s3.object.key.replace(/\+/g, ' '));
6 const size = record.s3.object.size;
7 const eventName = record.eventName; // e.g., 'ObjectCreated:Put'
8
9 console.log(`${eventName}: s3://${bucket}/${key} (${size} bytes)`);
10 await processUpload(bucket, key);
11 }
12}| Event type | Triggers when |
|---|---|
s3:ObjectCreated:* | PUT, POST, COPY, CompleteMultipartUpload |
s3:ObjectRemoved:* | DELETE, DeleteMarkerCreated |
s3:ObjectRestore:* | Glacier restore initiated or completed |
s3:Replication:* | Replication failure, missed threshold |
s3:LifecycleExpiration:* | Lifecycle rule deleted an object |
S3 → EventBridge is preferred when you need: content-based filtering, multiple targets per event, archiving, or replay.
Versioning
| Operation | Result |
|---|---|
| PUT object | New version created; previous version retained |
| GET object (no version) | Returns AWSCURRENT (latest) |
| GET object + VersionId | Returns specific version |
| DELETE object (no version) | Inserts delete marker; previous versions still exist |
| DELETE specific VersionId | Permanently removes that version |
| DELETE the delete marker | Restores the object (previous version becomes current) |
1# List all versions of an object
2aws s3api list-object-versions --bucket my-bucket --prefix photos/alice.jpg
3
4# Restore a previous version (copy it to become current)
5aws s3 cp s3://my-bucket/photos/alice.jpg s3://my-bucket/photos/alice.jpg --version-id abc123
6
7# Permanently delete a specific version
8aws s3api delete-object --bucket my-bucket --key photos/alice.jpg --version-id abc123MFA Delete: Requires MFA token to permanently delete object versions or change bucket versioning state. Must be enabled by the root account.
Encryption Options
| Method | Key managed by | API call per object | Header |
|---|---|---|---|
| SSE-S3 | S3 (AES-256) | No | x-amz-server-side-encryption: AES256 |
| SSE-KMS | KMS CMK | Yes (GenerateDataKey) | x-amz-server-side-encryption: aws:kms |
| DSSE-KMS | KMS CMK (dual layer) | Yes | x-amz-server-side-encryption: aws:kms:dsse |
| SSE-C | You (per request) | No | x-amz-server-side-encryption-customer-key |
| Client-side | You (before upload) | Optional KMS | None |
1# Enforce encryption at rest — deny unencrypted PUTs via bucket policy
2aws s3api put-bucket-policy --bucket my-bucket --policy '{
3 "Version": "2012-10-17",
4 "Statement": [{
5 "Sid": "DenyUnencryptedObjectUploads",
6 "Effect": "Deny",
7 "Principal": "*",
8 "Action": "s3:PutObject",
9 "Resource": "arn:aws:s3:::my-bucket/*",
10 "Condition": {
11 "StringNotEquals": {
12 "s3:x-amz-server-side-encryption": "aws:kms"
13 }
14 }
15 }]
16}'
17
18# Enforce HTTPS-only access
19aws s3api put-bucket-policy --bucket my-bucket --policy '{
20 "Statement": [{
21 "Effect": "Deny",
22 "Principal": "*",
23 "Action": "s3:*",
24 "Resource": ["arn:aws:s3:::my-bucket", "arn:aws:s3:::my-bucket/*"],
25 "Condition": { "Bool": { "aws:SecureTransport": "false" } }
26 }]
27}'Access Control
Public Access Block
Always-on protection at the bucket or account level:
1# Block ALL public access on a bucket (recommended default)
2aws s3api put-public-access-block --bucket my-bucket --public-access-block-configuration BlockPublicAcls=true,IgnorePublicAcls=true, BlockPublicPolicy=true,RestrictPublicBuckets=trueBucket Policies vs ACLs vs IAM
| Bucket Policy | IAM Policy | ACL | |
|---|---|---|---|
| Type | Resource-based | Identity-based | Legacy per-object/bucket |
| Cross-account | ✅ direct | ✅ via role | ✅ (legacy) |
| Conditions | ✅ full | ✅ full | ❌ |
| Recommended | ✅ | ✅ | ❌ (disable ACLs) |
Cross-Account Access Pattern
1{
2 "Version": "2012-10-17",
3 "Statement": [{
4 "Sid": "AllowCrossAccountRead",
5 "Effect": "Allow",
6 "Principal": { "AWS": "arn:aws:iam::987654321098:role/DataPipelineRole" },
7 "Action": ["s3:GetObject", "s3:ListBucket"],
8 "Resource": [
9 "arn:aws:s3:::my-data-bucket",
10 "arn:aws:s3:::my-data-bucket/*"
11 ]
12 }]
13}Lifecycle Policies
Automatically transition objects between storage classes or expire them:
1{
2 "Rules": [
3 {
4 "ID": "IntelligentTieringAfter30Days",
5 "Status": "Enabled",
6 "Filter": { "Prefix": "logs/" },
7 "Transitions": [
8 { "Days": 30, "StorageClass": "INTELLIGENT_TIERING" },
9 { "Days": 90, "StorageClass": "GLACIER" },
10 { "Days": 365, "StorageClass": "DEEP_ARCHIVE" }
11 ],
12 "Expiration": { "Days": 2555 }
13 },
14 {
15 "ID": "CleanupIncompleteMultipartUploads",
16 "Status": "Enabled",
17 "Filter": {},
18 "AbortIncompleteMultipartUpload": { "DaysAfterInitiation": 7 }
19 },
20 {
21 "ID": "ExpireOldVersions",
22 "Status": "Enabled",
23 "Filter": {},
24 "NoncurrentVersionExpiration": { "NoncurrentDays": 30 },
25 "NoncurrentVersionTransitions": [
26 { "NoncurrentDays": 7, "StorageClass": "STANDARD_IA" }
27 ]
28 }
29 ]
30}S3 Replication
| CRR | SRR | |
|---|---|---|
| Region | Different region | Same region |
| Use case | DR, latency, compliance | Log aggregation, test/prod sync |
| Versioning required | ✅ both buckets | ✅ both buckets |
| Delete markers replicated | Optional | Optional |
| Existing objects | Not replicated by default (use S3 Batch Operations) | Same |
Replication requires: versioning enabled on both source and destination, and an IAM role with permissions to read source and write destination.
S3 Select & Glacier Select
S3 Select uses SQL to filter data inside S3 — only the matching rows are transferred:
1import { SelectObjectContentCommand } from '@aws-sdk/client-s3';
2
3const response = await s3.send(new SelectObjectContentCommand({
4 Bucket: 'my-bucket',
5 Key: 'data/orders-2024.csv',
6 ExpressionType: 'SQL',
7 Expression: "SELECT * FROM S3Object WHERE status = 'failed' AND amount > 1000",
8 InputSerialization: {
9 CSV: { FileHeaderInfo: 'USE', RecordDelimiter: '\n', FieldDelimiter: ',' },
10 CompressionType: 'GZIP',
11 },
12 OutputSerialization: { CSV: {} },
13}));
14
15// Stream the filtered results
16for await (const event of response.Payload) {
17 if (event.Records?.Payload) {
18 process.stdout.write(Buffer.from(event.Records.Payload));
19 }
20}Supported formats: CSV, JSON, Parquet. Supports GZIP and BZIP2 compression.
CORS Configuration
CORS is required when a browser makes direct S3 requests from a different origin:
1[
2 {
3 "AllowedHeaders": ["*"],
4 "AllowedMethods": ["GET", "PUT", "POST", "DELETE", "HEAD"],
5 "AllowedOrigins": ["https://app.example.com"],
6 "ExposeHeaders": ["ETag", "x-amz-request-id"],
7 "MaxAgeSeconds": 3000
8 }
9]1aws s3api put-bucket-cors --bucket my-bucket --cors-configuration file://cors.jsonWhen you need CORS: Browser JavaScript calls S3 directly (e.g., pre-signed PUT for direct upload). Not needed when your backend proxies S3 requests.
Transfer Acceleration & Other Performance Features
| Feature | What it does | URL / config |
|---|---|---|
| Transfer Acceleration | Routes uploads via CloudFront edge → AWS backbone | bucket.s3-accelerate.amazonaws.com |
| Byte-range fetch | Download specific bytes in parallel (multipart-style download) | Range: bytes=0-1048575 header |
| Requester Pays | Requester pays data transfer costs (not bucket owner) | Bucket setting |
| S3 Express One Zone | Single-AZ, sub-millisecond latency for ML/HPC workloads | Different bucket type (--create-bucket-configuration LocationConstraint) |
1# Enable Transfer Acceleration
2aws s3api put-bucket-accelerate-configuration --bucket my-bucket --accelerate-configuration Status=Enabled
3
4# Upload via accelerated endpoint
5aws s3 cp large-file.zip s3://my-bucket/ --endpoint-url https://my-bucket.s3-accelerate.amazonaws.comObject Lock (WORM)
Object Lock prevents objects from being deleted or overwritten — used for compliance and ransomware protection.
| Mode | Override possible? | Who can override |
|---|---|---|
| Compliance | ❌ No one | No one (not even root or AWS) |
| Governance | ✅ | Users with s3:BypassGovernanceRetention permission |
| Legal Hold | ✅ | Users with s3:PutObjectLegalHold permission |
DVA-C02 Quick Reference
| Topic | Key Fact |
|---|---|
| S3 consistency | Strong read-after-write for all operations (since Dec 2020) |
| Max object size | 5 TB |
| Max single PUT | 5 GB (use multipart for larger) |
| Multipart recommended | Objects > 100 MB |
| Multipart required | Objects > 5 GB |
| Pre-signed URL max (IAM user) | 7 days |
| Pre-signed URL max (IAM role) | 12 hours (STS session cap) |
| SSE-S3 header | x-amz-server-side-encryption: AES256 |
| SSE-KMS header | x-amz-server-side-encryption: aws:kms |
| SSE-KMS KMS call | GenerateDataKey per object |
| SSE-C requirement | HTTPS mandatory |
| Versioning DELETE | Creates delete marker — does not remove versions |
| MFA Delete | Requires MFA to permanently delete versions |
| S3 Event notification targets | SQS, SNS, Lambda, EventBridge |
| Lifecycle transition min duration | 30 days in Standard before moving to Standard-IA |
| Replication requires | Versioning on both source and destination buckets |
| Existing objects replicated | No — use S3 Batch Operations |
| S3 Select formats | CSV, JSON, Parquet |
| Transfer Acceleration URL | bucket.s3-accelerate.amazonaws.com |
| Object Lock Compliance mode | No one can delete before retention (not even root) |
| Object Lock Governance override | s3:BypassGovernanceRetention permission |
| CORS purpose | Browser direct S3 requests from different origin |
| S3 Express One Zone | Sub-millisecond latency, single AZ, ML/HPC workloads |
Practice Questions6
Q1. A developer needs to allow a web browser to make cross-origin requests to an S3 bucket to display images. The browser shows a CORS error. What must the developer configure?
Select one answer before revealing.
Q2. A developer uses the S3 SDK to upload 100 MB files. Uploads frequently fail on slow connections. What S3 feature should the developer use to improve reliability?
Select one answer before revealing.
Q3. A developer needs to generate a temporary URL allowing an unauthenticated user to download a specific S3 object for exactly 15 minutes. Which S3 feature should be used?
Select one answer before revealing.
Q4. A developer accidentally deleted 1,000 S3 objects. The bucket does not have versioning enabled. Can the objects be recovered?
Select one answer before revealing.
Q5. A developer needs to store sensitive medical records in S3. The data must be encrypted with a customer-managed key, and all key usage must be auditable. The developer also needs to rotate the key annually. Which S3 encryption configuration satisfies all requirements?
Select one answer before revealing.
Q6. A developer needs to trigger a Lambda function every time a new object is uploaded to an S3 bucket. Which S3 feature should be configured?
Select one answer before revealing.