Laptop to Production

Files on S3

Menu photos, receipts and storage that never fills up.

Time
35 minutes
Level
Beginner

Part 1: The problem

QuickCart, the week the menu photos broke

QuickCart’s app shows a photo for every dish, and the web shop needs them too. Leo needed the photos reachable from anywhere, so he made the bucket they lived in public. It worked straight away.

Months later, the same bucket was also holding customers’ receipts, because it was the bucket that already existed. They were public too.

That same week, a script meant to upload new photos to a test folder ran against the real one. It overwrote every dish photo with a placeholder. The bucket had no versioning, so the old photos were simply gone. The restaurants had to send them again.

So QuickCart needs its files stored where they are private by default, where access is handed out one file at a time, and where an overwrite or a delete can be undone.

Part 2: The idea

S3, Simple Storage Service, stores files. You put a file in, you get it back by name, and you never think about disks: there’s no size to choose and nothing fills up. QuickCart uses it for menu photos, receipts and backups.

Buckets, objects and keys

WordWhat it isQuickCart example
BucketA container for files. It belongs to one regionquickcart-menu-111122223333
ObjectOne file, plus some details about it, like its size and typea photo of a burger
KeyThe object’s full name inside the bucketdishes/burger.jpg

Bucket names are shared across every AWS account in the world, so quickcart-menu is probably taken. Adding your account ID to the end keeps yours unique. Names are lower case, with letters, numbers, dots and hyphens.

S3 has no real folders. The key receipts/2026/order-1001.pdf is one name that happens to contain slashes. The console shows the part before each slash as a folder so it’s easier to browse, but nothing called receipts exists on its own.

Private until you say otherwise

A new bucket and everything in it can be read only by your own account. There are three proper ways to let something else in:

WayWhat it givesQuickCart uses it for
An IAM role’s policyA server or container can read and write the bucketThe API storing receipts. Who can do what: IAM covers roles
A bucket policyRules attached to the bucket itself, for everyone who tries to use itRefusing any request that isn’t over HTTPS
A presigned URLA link to one object that works for a limited time, for anyone who has itA customer downloading their own receipt

Making the bucket public is not on that list. Nothing QuickCart does needs anonymous access to the bucket. When a website needs to show files from S3 directly, the usual answer is a CDN such as CloudFront reading the private bucket, not a public bucket.

Block Public Access is four switches, all on by default, that override any policy or setting that would make the bucket or its objects public. Leave them on. If something seems to need them off, that’s the sign to use one of the three ways above instead.

Key point: An S3 bucket is private until you make it otherwise: keep Block Public Access on, and hand out access with roles and presigned URLs, never by making the bucket public.

Presigned URLs

A presigned URL is an ordinary link to one object, with a signature and an expiry time added to the end. The signature is made with the credentials of someone who is allowed to read the object, usually the app’s role. Anyone holding the link can download that one object until it expires. After that, S3 refuses it.

DetailWhat it means
ExpiryYou choose it. Up to 12 hours from the console, and up to 7 days when signed with a long-lived key
Signed by a roleThe link also stops working when that role’s temporary credentials expire, which may be sooner
PermissionsThe link can do only what the signer could do. If the signer loses access, the link stops working
Anyone with itIt isn’t tied to a person. Keep expiry short, like five minutes, for anything private

QuickCart’s app makes a fresh five-minute link each time a customer taps Download receipt. The bucket is never public, and an old link shared by mistake stops working almost at once.

Versioning: an undo button for files

With versioning on, S3 keeps every version of every object.

You doVersioning offVersioning on
Upload a file with an existing keyThe old one is replaced for goodThe old one is kept as an older version. The new one becomes current
Delete a fileIt’s gone for goodS3 adds a delete marker on top. Every version is still underneath
Undo a deleteImpossibleDelete the delete marker
Get an older version backImpossibleCopy it back on top, and it becomes the current version

Two things to know. First, versioning only protects changes made after you turn it on, so turn it on when you create the bucket. Second, once on, it can’t be turned off again, only suspended: new uploads stop being versioned, but the versions already kept stay.

Every version is billed as storage, so a bucket that’s overwritten often grows. A lifecycle rule, below, cleans up old versions after a while.

Encryption

Since January 2023, S3 encrypts every new object automatically with SSE-S3: keys that AWS creates, holds and rotates for you. You don’t have to do anything, and there’s no extra charge.

OptionWho holds the keyWhen to choose it
SSE-S3AWS, entirelyThe default. Right for most things
SSE-KMSA key in KMS that you controlWhen you need to decide who can use the key, or see every use of it in CloudTrail. KMS requests cost a little

That’s encryption at rest, on AWS’s disks. For encryption in transit, every request should use HTTPS. A bucket policy can refuse any request that doesn’t, as you’ll do in the lab.

Storage classes

Every object has a storage class, which trades the monthly price against how quickly and cheaply you can read it back.

ClassGood forThe catch
StandardAnything read often: menu photos. The defaultThe highest price per GB, about $0.023 a month in us-east-1
Intelligent-TieringData whose access you can’t predictA small monthly fee per object to watch it
Standard-IARead rarely but needed at once: last year’s receiptsA fee for every GB you read, and you pay for at least 30 days
One Zone-IARarely read data you could recreateKept in one availability zone, so it can be lost with that zone
Glacier Instant RetrievalArchives read a few times a yearHigher fee to read; at least 90 days
Glacier Flexible RetrievalArchives you can wait minutes to hours forYou request a restore and wait
Glacier Deep ArchiveRecords kept for years for legal reasonsThe cheapest; restores take up to 12 hours

Prices change, and vary by region; check the S3 pricing page for current figures.

Lifecycle rules

A lifecycle rule moves or deletes objects on a schedule, so nobody has to. QuickCart’s rules:

RuleWhy
Move receipts to Standard-IA 30 days after uploadAlmost nobody opens a receipt after the first month
Permanently delete older versions 30 days after they’re replacedVersioning protects against mistakes, but a month is long enough to notice one

Bucket policies

A bucket policy is a JSON document attached to the bucket, in the same format as the IAM policies you’ve already seen. The difference is where it lives: an IAM policy says what one identity can do, while a bucket policy says what anyone can do to this bucket. This one refuses every request that doesn’t use HTTPS:

bucket-policy.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyPlainHttp",
      "Effect": "Deny",
      "Principal": "*",
      "Action": "s3:*",
      "Resource": [
        "arn:aws:s3:::quickcart-menu-111122223333",
        "arn:aws:s3:::quickcart-menu-111122223333/*"
      ],
      "Condition": {
        "Bool": {
          "aws:SecureTransport": "false"
        }
      }
    }
  ]
}
FieldWhat it does
VersionThe version of the policy language. Always 2012-10-17
SidA name for the statement, for people reading it. Optional
Effect: DenyThis statement refuses requests. A Deny always beats an Allow
Principal: *It applies to everyone, including your own account
Action: s3:*Every S3 action
ResourceTwo lines: the bucket itself, and /* for every object in it
ConditionOnly when this is true…
aws:SecureTransport: false…the request was not made over HTTPS

Part 3: See it

Below is one key, menu.jpg, and every version S3 is keeping for it. Upload a photo twice, delete it, then get it back. Then switch versioning off and try the same thing.

Versioning
quickcart-menu / menu.jpg Versioning on

Every version S3 is keeping, newest at the top

  • No versions. As far as S3 knows, menu.jpg has never existed.
The app asks for menu.jpg and gets404 Not Found — NoSuchKey
ReadyThe bucket is empty. Choose Upload a menu photo twice, then delete it.

What to notice: with versioning on, the delete didn’t remove anything. It added a marker on top, and deleting the marker was the whole undo. With versioning off, the second upload had already destroyed the first photo, and there was nothing to restore after the delete.

Part 4: Do it

Pennies — a few small files in S3 Standard cost a fraction of a cent a month, and a handful of requests less than that.

You’ll create a private, versioned bucket, see that its objects are private, share one with a presigned URL, overwrite and delete a photo and bring it back, add a lifecycle rule, and add the HTTPS-only policy. About 25 minutes.

Before you start, have two small images on your laptop, both renamed to menu.jpg, in two different folders. Any photos will do. Check the region selector at the top right of the console says N. Virginia (us-east-1).

  1. Create the bucket

    Find your account ID: open the account menu at the top right of the console and copy the 12-digit Account ID. In this course it’s shown as 111122223333; use yours instead.

    In the AWS console: S3General purpose bucketsCreate bucket

    FieldEnter
    Bucket typeGeneral purpose
    Bucket namequickcart-menu- followed by your account ID, like quickcart-menu-111122223333
    Object OwnershipACLs disabled — the default
    Block all public accessOn — the default. Leave all four boxes ticked
    Bucket VersioningEnable
    Default encryptionServer-side encryption with Amazon S3 managed keys (SSE-S3) — the default

    Choose Create bucket.

    You should see

    A green banner saying the bucket was created, and the bucket in the list.

  2. Upload the first photo

    In the AWS console: S3your bucketObjectsUpload

    Choose Add files, pick the first menu.jpg, and choose Upload.

    You should see

    Upload succeeded. Choose Close, and menu.jpg is in the Objects list.

  3. See that it's private

    Choose menu.jpg to open its details. Copy the Object URL and paste it into a new browser tab.

    You should see

    Not the photo, but a short XML page containing <Code>AccessDenied</Code>. The object has an address, but nobody may read it without permission. That’s the default, and it’s what you want.

  4. Share it with a presigned URL

    In the AWS console: S3your bucketmenu.jpgObject actionsShare with a presigned URL

    FieldEnter
    Time interval until the presigned URL expiresMinutes
    Number of minutes5

    Choose Create presigned URL. The console copies the link to your clipboard. Paste it into a browser tab.

    You should see

    The photo. Look at the address bar: the same Object URL as before, followed by a long string of settings including X-Amz-Expires=300, which is 300 seconds, and X-Amz-Signature.

    Wait five minutes and reload the tab.

  5. Overwrite the photo

    Upload the second menu.jpg, the same way as the first. It has the same key, so it replaces the first as the current version.

    Back in the Objects list, turn on Show versions.

    You should see

    Two rows for menu.jpg, each with a different Version ID. The newer one is marked as the current version. The first photo wasn’t lost; it’s kept underneath.

  6. Delete it

    Turn Show versions off. Tick menu.jpg and choose Delete. Type delete in the box and choose Delete objects.

    You should see

    The Objects list is empty. As far as the app is concerned, menu.jpg is gone.

    Now turn Show versions on again.

    You should see

    Three rows: the two photos, and on top a row of type Delete marker. The delete removed nothing; it added the marker.

  7. Bring it back

    With Show versions on, tick only the delete marker and choose Delete. Deleting a specific version is permanent, so the console asks you to type permanently delete. Type it and choose Delete objects.

    You should see

    Turn Show versions off. menu.jpg is back, showing the second photo. Removing the marker made the newest version underneath current again.

  8. Add a lifecycle rule

    In the AWS console: S3your bucketManagementCreate lifecycle rule

    FieldEnter
    Lifecycle rule nametidy-old-versions
    Choose a rule scopeApply to all objects in the bucket, and tick the box acknowledging it
    Lifecycle rule actionsTick Transition current versions of objects between storage classes and Permanently delete noncurrent versions of objects
    Transition current versions: Choose storage class transitionsStandard-IA
    Days after object creation30
    Permanently delete noncurrent versions: Days after objects become noncurrent30
    Number of newer versions to retainleave empty

    If the console asks you to acknowledge a cost for transitioning small objects, tick it. Choose Create rule.

    You should see

    The rule listed under Lifecycle rules as Enabled. At the bottom of the form, the console draws a timeline of what happens on day 0 and day 30.

  9. Refuse anything that isn't HTTPS

    In the AWS console: S3your bucketPermissionsBucket policyEdit

    Paste the policy from the idea, with your account ID in place of 111122223333 in both Resource lines. Choose Save changes.

    You should see

    The policy shown under Bucket policy. Block Public Access stays On: a Deny never makes a bucket public, so it’s allowed.

  10. Clean-up step: Clean up

    A bucket must be empty before it can be deleted, and a versioned bucket holds every version and delete marker too. The Empty button removes all of them.

    In the AWS console: S3General purpose bucketsyour bucketEmpty

    Type permanently delete and choose Empty.

    In the AWS console: S3General purpose bucketsyour bucketDelete

    Type the bucket’s name and choose Delete bucket.

    You should see

    The bucket is gone from the list. Nothing is left that costs money.

Part 5: Check yourself

Customers need to download their own receipts, which are stored in a private bucket. What's the right way?

Someone deletes menu.jpg from a bucket that has versioning on. What actually happens?

Versioning was off when a bad sync overwrote every menu photo. You turn versioning on now. What can you recover?

What does the key "receipts/2026/order-1001.pdf" tell you about folders in S3?

QuickCart keeps order receipts for seven years, but almost nobody opens one after the first month. What saves money without deleting anything?

Part 6: Cheat sheet

On one card
Term or settingIn one line
BucketA container for objects, in one region. Its name is unique worldwide
Object and keyA file, and its full name. The slashes in a key aren’t real folders
Block Public AccessOn by default. Leave it on
AccessIAM roles for servers, bucket policies for rules, presigned URLs for one file at a time
Presigned URLA link to one object that expires. Keep it short: minutes, not days
VersioningTurn it on when you create the bucket. It only protects later changes
Delete markerWhat a delete adds when versioning is on. Delete the marker to undo
SSE-S3Default encryption on every new object. No setup, no charge
HTTPS onlyBucket policy: Deny when aws:SecureTransport is false
Storage classesStandard for often, Standard-IA for rarely, Glacier for archives
Lifecycle ruleMove to cheaper classes and delete old versions on a schedule
Delete a bucketEmpty first, then Delete

Finished the build and the quiz? Mark it done to fill in its stop on the route map.