AWS S3 Reference
A reference for AWS S3: buckets, bucket policies, storage classes, and the AWS CLI.
Infrastructure
Buckets and objects
S3 is flat object storage, not a filesystem — a bucket holds objects addressed by key, and a key like uploads/2026/invoice.pdf only looks like a folder path. There's no real directory structure underneath; the console and CLI just render the / characters in keys as folders for convenience. Bucket names are globally unique across all of AWS, not just your account, which is why an obvious name is often already taken.
Public access is blocked by default
New buckets have Block Public Access enabled account-wide by default — a deliberate guardrail after years of accidental data leaks from open buckets. Serving public files (a CDN origin, public assets) means explicitly turning specific block-public-access settings off and attaching a bucket policy that grants read access; either one alone isn't enough.
Bucket policy example
A bucket policy is a JSON document, similar in shape to an IAM policy, attached directly to the bucket. This one allows public read of everything under public/ while leaving the rest of the bucket private:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadOnly",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::my-bucket/public/*"
}
]
}
Presigned URLs
For anything that shouldn't be public — a private user upload, a download link that expires — a presigned URL grants temporary access to one object without changing the bucket's own permissions at all. Laravel's S3 filesystem driver generates these directly:
use Illuminate\Support\Facades\Storage;
$url = Storage::disk('s3')->temporaryUrl(
'private/report.pdf',
now()->addMinutes(10)
);
The URL embeds a signature computed from your credentials and the expiry time — anyone with the link can access the object until it expires, but can't derive your credentials from it or extend the expiry.
Storage classes
| Class | Use for |
|---|---|
| Standard | Frequently accessed data — the default, and the right choice unless you have a specific reason to change it. |
| Standard-IA / One Zone-IA | Infrequently accessed but needed instantly when it is — backups, older user uploads. Cheaper storage, a per-GB retrieval fee. |
| Glacier / Glacier Deep Archive | Archival data retrieved rarely, where a retrieval delay (minutes to hours) is acceptable — compliance archives, long-term backups. |
| Intelligent-Tiering | Automatically moves objects between tiers based on actual access patterns — useful when access patterns are unpredictable and you don't want to manage lifecycle rules by hand. |
A lifecycle rule (Bucket → Management → Lifecycle rules) automates the move between classes — e.g. transition to Standard-IA after 30 days, Glacier after 90, delete after 365 — rather than managing it per-object.
AWS CLI
aws s3 ls s3://my-bucket/uploads/
aws s3 cp local-file.jpg s3://my-bucket/uploads/local-file.jpg
aws s3 sync ./dist s3://my-bucket/assets --delete
aws s3 rm s3://my-bucket/uploads/old-file.jpg
sync only transfers changed files and, with --delete, removes anything in the destination that no longer exists in the source — the standard way to publish a built static site or asset bundle to a bucket.
Versioning
Enabling versioning on a bucket keeps every prior version of an object instead of overwriting it on each upload, which turns an accidental overwrite or delete into something recoverable rather than destructive. It's off by default and, once enabled, can't be fully disabled again (only suspended) — worth turning on for any bucket holding data you can't regenerate.