Object Storage You Control, Without S3 Lock-In
How to get object storage you control without S3 lock-in, using the S3 API as a portable standard so your files can move between providers without a rewrite.
You can have durable, cheap object storage for your files without chaining your app to one cloud provider. The trick is to treat the S3 API as a portable standard rather than a single company's product, so your uploads, backups, and assets can move between providers, or onto your own storage, without changing a line of application code. Here is how I keep object storage owned and swappable across a portfolio, and why that matters more than shaving a few cents per gigabyte.
The insight is that S3 is two different things wearing one name: a protocol, which is everywhere, and a specific vendor's service, which is one place. Build against the protocol and you are free. Build against the service and you are stuck.
Why object storage is a quiet lock-in trap
Files feel harmless to store anywhere. Then two things tie you down. The first is your code: if it calls one provider's proprietary SDK with proprietary features, every upload path in your app assumes that vendor. The second is gravity: once terabytes live in one bucket, moving them means paying egress fees to pull your own data out, which is a tax specifically designed to make leaving expensive.
That combination is why so many teams stay on storage they have outgrown. The data is heavy and the code is welded. It is the same capture mechanism I describe in what no vendor lock-in actually requires, applied to your files instead of your compute.
How to keep object storage portable
Two rules, and they cost you nothing to follow from day one.
Rule one: code against the S3 API, never a proprietary extension. The S3 protocol is implemented by many providers and by open-source servers you can host yourself. Use a standard S3 client, use only the common operations, and your application does not know or care which provider is behind the endpoint. Switching providers becomes a change to a configuration value: endpoint, key, bucket. No code touched.
Rule two: pick providers with cheap or zero egress, so gravity never traps your data. Some object-storage providers do not charge you to read your own files out. That single choice keeps the exit door open, because leaving costs a copy operation instead of a ransom. This is the same discipline I apply to keeping the whole stack movable in the deploy you actually control.
Should you self-host the storage itself
Sometimes yes, sometimes no, and the S3-API approach means you do not have to decide up front.
For most portfolio apps, an S3-compatible provider with no egress fees is the sweet spot: durable, cheap, someone else handles the disks, and because you coded against the standard you can leave whenever you want. That is not lock-in; that is a swappable utility.
For data you want fully on your own metal, for privacy, residency, or cost at large volume, you run an open-source S3-compatible server on your own box, and your app talks to it through the exact same client. The endpoint changes, nothing else does. This is the reliability-and-control question I worked through in is self-hosting reliable enough: own what needs owning, rent what is fine to rent, and keep the seam standard so the choice stays reversible.
Backups and assets deserve the same rule
The S3-API discipline is not just for user uploads. My off-site backups land in S3-compatible storage on a different provider from the boxes, encrypted, and because they use the standard protocol I could restore them anywhere. My static assets sit in a bucket a CDN reads from, and that bucket could move providers without the CDN or the app noticing.
Everything that touches object storage goes through one standard seam. That means no single storage vendor is load-bearing anywhere in my portfolio, which is the whole point of owning your stack, and the same reason I run servers that cannot be held hostage.
Build the seam once, reuse it everywhere
The reason this scales across twenty apps is that the storage layer is built once and inherited. Every app uses the same S3 client wrapper pointed at the same kind of endpoint. Provisioning storage for a new product is creating a bucket and setting three config values. There is no per-app storage architecture to design, and no per-app lock-in to worry about later.
I bundled this portable object-storage pattern into my infrastructure stack, HostSSH, so a new app on the fleet gets standards-based, provider-independent storage the day it ships. Cheap files are nice. Files you can pick up and carry to any provider, or onto your own disk, without a rewrite are the actual goal. Code against the standard, avoid the egress trap, and object storage becomes a utility you use instead of a vendor that owns you.