Store files as objects in a bucket and address each one by a URL — the scalable, durable home for your uploads, images, and backups off the VPS.
← / → to navigate · EN/VI switch below
Forget folders and a disk. You put files in a container and the storage service hands you a link.
An object is a file plus its metadata (size, content-type, tags) — stored as one unit.
A bucket is a flat name-space that holds objects. There are no real folders, just paths in the key.
Each object is addressed by a plain HTTP URL, so browsers, CDNs, and other services can fetch it directly.
Images, user uploads, video, and backups are natural fits — anything you want to serve over the internet.
You talk to it over the network with an API — upload, list, download, delete. It scales and it's durable.
A REST/S3 API: put an object, get it back, list a bucket, delete it. Works from the CLI, an SDK, or raw HTTP.
The key is the object's name (e.g. uploads/101.png). The URL is the stable address that serves it.
It spans thousands of disks across many machines. You never plan capacity — add a billion objects and it just works.
Usually cheaper than a VPS disk, and data is replicated so it survives hardware failure (S3 promises 11 nines of durability).
Everything in object storage is just these four ideas.
Four names, one mental model. Learn them and the whole thing makes sense.
A named container (a name-space) that holds objects. e.g. myapp-uploads. One account can have many.
A single file plus its metadata (content-type, size, custom tags). The thing you actually store.
The object's name inside the bucket — the path you see. e.g. avatars/101.png. Must be unique in the bucket.
The HTTP address that serves the object. e.g. https://myapp.s3.../avatars/101.png. This is what you put in your app.
A bucket holds objects; each object's key becomes part of its URL. That's the whole system.
The moment your files shouldn't live on the VPS disk, this is the answer.
Store what users upload off the app disk, then serve it by URL — never bloating your VPS.
Video, audio, generated reports, fonts — anything large that users fetch directly.
Copy your DB dumps and configs somewhere durable, so a dead VPS disk doesn't take your data with it.
It's built to be fetched over the internet; put a CDN in front and delivery is fast worldwide.
One is the cloud classic, one you can run yourself on a VPS.
The original and the standard. Simple Storage Service — the API everyone else copies. Free tier, pay per use.
Open-source, self-hosted, S3-compatible. Run it in a container on your own VPS and use the exact same S3 tools.
Because MinIO speaks S3, code written for S3 runs against MinIO unchanged — just point the endpoint at your VPS.
S3 = rent it from AWS. MinIO = own it on your box. Same mental model, same commands, different landlord.
Same API and same commands — the only real difference is who runs it.
| Aspect | AWS S3 | MinIO |
|---|---|---|
| Runs on | AWS cloud — you rent it | Your VPS — you host it |
| Cost | Pay per GB stored + requests | Just your existing VPS bill |
| API | S3 — the original | S3-compatible — same SDKs / CLI |
| Setup | Create a bucket, point at it | Run one container, create a bucket |
| Best for | Production, hands-off, no ops | A VPS, full control, data stays yours |
aws s3 cp is the one-liner you'll use most: local path in, bucket path out.
# copy a local file into a bucket (this is the whole upload)
aws s3 cp ./avatar.png s3://myapp-uploads/avatars/101.png
# make a public bucket, then list what's inside it
aws s3 mb s3://myapp-uploads --acl public-read
aws s3 ls s3://myapp-uploads/avatars/
Point the same commands at MinIO by setting an endpoint URL — nothing else changes.
Create a bucket, upload one object, list it — and here's the URL it lives at.
put_object is the whole upload in Python. Swap the client for a MinIO endpoint and this exact code runs on your VPS.
import boto3
s3 = boto3.client("s3")
s3.put_object(
Bucket="myapp-uploads",
Key="posts/778.jpg",
Body=open("post.jpg", "rb").read(),
)
# now it's live — the same S3 API works against MinIO too:
print("https://myapp-uploads.s3.amazonaws.com/posts/778.jpg")
For MinIO: boto3.client("s3", endpoint_url="http://your-vps:9000", ...).
The payoff is a plain link. Three ways you'll actually serve these.
If the bucket allows public reads, https://myapp-uploads.s3.../key is live — drop it straight into an <img src>. Simple, but anyone can see it.
For private buckets, generate a time-limited signed link a user can fetch — no public access needed.
The common production setup: S3 (or MinIO) behind a CDN — it caches and serves the object fast from near each user.
Put it next to the VPS disk and the reason to use it becomes obvious.
One disk, one machine. Fills up, and if it dies your uploads, images, and backups go with it. You plan the capacity yourself.
Fine for code. The wrong home for files people fetch.
A bucket of objects, each with a URL. Scales without you doing anything, replicates so it's durable, and a CDN in front makes it fast.
Your off-VM home for uploads and media.
Object storage is your files off the VM — a bucket of objects, each with a URL, that scales on its own.
Next: put a CDN in front so delivery is fast worldwide.