After this lesson, you will be able to: Use S3 for storage: buckets, objects, bucket policies, presigned URLs, static website hosting, and lifecycle rules.
S3 is the most-used AWS service. Knowing it deeply pays off in every project.
Bucket: a container of objects. Globally unique name (across all of AWS). Lives in a specific region. Object: a file. Keys can look like paths (`uploads/2026/file.png`) but there are no real directories. Storage classes: Standard (default), Intelligent-Tiering (auto-optimize), Standard-IA (infrequent access, cheaper retrieval), Glacier (cheap archive, slow retrieve). Default durability: 11 nines (99.999999999%). Practically: assume objects never disappear.
After `aws configure`, you can pipe to/from S3.
aws s3 mb s3://my-bucket-name # create bucket (globally unique name)aws s3 ls # list bucketsaws s3 ls s3://my-bucket-name # list objectsaws s3 cp file.txt s3://my-bucket-name/ # uploadaws s3 cp s3://my-bucket-name/file.txt . # downloadaws s3 sync ./local-dir s3://my-bucket-name/dest # rsync-likeaws s3 rm s3://my-bucket-name/file.txt # deleteaws s3 rb s3://my-bucket-name # delete empty bucketaws s3 rb s3://my-bucket-name --force # force-delete with contents
IAM policy: 'this PRINCIPAL can do these actions on these resources'. Attached to user/role. Bucket policy: 'this RESOURCE allows these principals to do these actions'. Attached to the bucket. Both can grant access; both can be evaluated. Cross-account access typically requires a bucket policy. Public buckets: set `Block Public Access` OFF on the bucket + bucket policy allowing `*` (anyone). DON'T do this unless intentional; misconfigured public buckets are the #1 cloud data leak.
Most common pattern for user uploads.
# Server (Node + AWS SDK v3):import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";import { getSignedUrl } from "@aws-sdk/s3-request-presigner";const client = new S3Client({ region: "us-east-1" });const command = new PutObjectCommand({Bucket: "uploads-bucket",Key: `uploads/${userId}/${Date.now()}-${filename}`,ContentType: contentType,});const presignedUrl = await getSignedUrl(client, command, { expiresIn: 600 }); // 10 minreturn presignedUrl; // give to client# Client:# fetch(presignedUrl, { method: 'PUT', body: file })# File uploads DIRECTLY to S3, never touches your server.# Same pattern with GetObjectCommand for downloads.
Configure on the bucket: 'objects older than 30 days → Standard-IA; older than 90 days → Glacier; older than 365 days → DELETE'. Massive cost savings for log buckets and archives. Same mechanism deletes incomplete multipart uploads (they accrue without cleanup). Set lifecycle rules on every bucket that stores data with predictable aging.
Public buckets by accident. Always check `Block Public Access` is ON for new buckets. Versioning off + no lifecycle. Versioning protects against accidental delete; lifecycle prevents version pileup costs. Server-side encryption off. Bucket default encryption is free; turn it on. Wrong region. Cross-region requests cost more + add latency. Pick your bucket's region intentionally. Ignoring incomplete multipart uploads, they linger forever, accruing storage cost, unless lifecycle cleans them up.
Pick the right pattern.
Sign in and purchase access to unlock this lesson.