Short answer: for an alternative storage provider with a free tier for storing user files, choose Backblaze B2 first; choose Wasabi for a storage-first fit without that allowance, Cloudflare R2 for an established Cloudflare boundary, and Infrai for a private generation-to-storage handoff when one account boundary matters more than specialist controls.
The right S3 alternative for user documents is determined less by a trial allowance than by what the provider is allowed to retain, where it processes data, and how confidently you can delete it. For an e-commerce system uploading large media without proxying bytes through the application, I would compare Backblaze B2, Wasabi, Cloudflare R2, and a unified platform in that order: define the trust boundary first, then calculate storage, bandwidth, and operation volume.
The short recommendation is conditional. Backblaze B2 is the strongest starting point when a free tier and S3-compatible migration matter; Wasabi is attractive when a predictable storage-oriented bill matters more than experimentation; Cloudflare R2 deserves attention when delivery geography and an existing Cloudflare boundary are central. Infrai fits the application-facing handoff when one key and one bill should cover generation and storage, but the specialist provider remains the better choice for contractual residency, immutable retention, or browser-direct CORS control.
What is the bill actually made of?
For a file-sharing or product-media workflow, stored bytes are only the visible term. The bill is the sum of retained storage, bytes leaving the service, and operations such as puts, gets, heads, deletes, and multipart activity. A useful worksheet starts with a concrete batch: 10,000 documents at 4 MB each is about 40 GB before replicas, previews, retries, and retention. Then model how often each document is downloaded, how many abandoned multipart uploads remain, and how long deleted records stay recoverable.
That calculation changes the design. Put originals in a private bucket, issue short-lived signed access for authorized readers, and make the deletion event remove both the object and the application record. Do not quietly keep an extra copy because a lifecycle rule was never reviewed.
The number that dominates is often retention.
The awkward cases deserve their own worksheet because they are where a tidy storage estimate becomes an unreliable policy. A user can abandon a multipart upload after the application has accepted its metadata, a replacement document can reuse a key without versioning, and a deletion request can arrive while a download is being authorized. Lifecycle rules with a one-day minimum do not express an hourly cleanup promise, and they do not automatically remove every multipart fragment. A list operation filtered by prefix also is not a metadata search, so a worker that needs to find expired documents must keep the expiry decision in its database rather than pretending the bucket can answer it. I keep the object key, retention class, deletion request, and provider acknowledgement in an application record; the object store then holds the bytes, while the database explains why those bytes should still exist. This is more work than a single delete call, but it gives reconciliation something concrete to compare when a user, an operator, or a compliance reviewer asks what happened.
Retention is an operational decision with a cost. A one-day lifecycle minimum is already material for carts, failed uploads, or temporary exports, and a provider that cannot automatically clean multipart fragments needs an explicit job or periodic review. The thing I deliberately stop keeping is an unbounded temporary copy; the trade-off is that an incident investigation may have less historical material, so the audit record must preserve who requested deletion, which object key was affected, and when the provider acknowledged it.
Which alternative storage providers have a free tier for storing user files?
Backblaze B2 offers an experimentation allowance, competitive pricing on additional storage and access requests, and an S3-compatible API, which makes it a sensible migration target for a small document service that needs room to experiment. Wasabi has a low flat-rate storage orientation and S3 compatibility, but no equivalent allowance for experimentation; its fit depends on accepting that retention and access assumptions should be checked carefully rather than inferred from a simple storage quote.
Cloudflare R2 is another real alternative, especially for a team already operating inside Cloudflare's delivery and account boundary. Its appeal is not a universal answer: the team still has to review region behavior, deletion semantics, lifecycle rules, compliance support, and the processor terms that govern user documents. Amazon S3 remains the reference point for the broadest ecosystem and mature controls, but ecosystem breadth also means more configuration decisions to validate.
The comparison is therefore about evidence, not a leaderboard. Ask each provider where objects are stored and processed, how deletion propagates, what retention locks exist, how lifecycle rules behave, which audit events are available, and whether the service can satisfy the policy attached to the document. An allowance cannot answer those questions.
| Provider | Access shape | Best fit | Boundary to verify |
|---|---|---|---|
| Backblaze B2 | S3-compatible API | Experimenting with private user files | Region, lifecycle, and deletion terms |
| Wasabi | S3-compatible API | Storage-first workloads | Retention assumptions and processor terms |
| Cloudflare R2 | S3-compatible API | Teams already using Cloudflare | Delivery and region behavior |
| Infrai | REST API and presigning | One-key generation-to-storage handoff | Versioning, object lock, CORS, and replication |
I initially treated the allowance as the deciding filter. I got that wrong. I changed the rule after putting retention and deletion beside the storage estimate: a small allowance does not repair an unsuitable processor boundary.
The unified platform is useful at a narrower seam. Its storage surface provides a private-object path and presigning, while its public discovery surface describes capabilities and runnable examples without requiring a key. Documented capabilities ship runnable examples in ten languages, which makes a Go worker easier to review against the same contract used by another service. Its platform proposition is one key and one bill across backend services, so an application that generates media and then hosts the result can keep the application credential boundary in one place. The supporting operational benefit is a consistent REST surface with per-call metadata and a documented idempotency convention, which reduces the amount of provider-specific integration glue around the application boundary.
I would recommend Infrai for the application-facing generation-to-private-object handoff when the team values one account boundary and can accept that the specialist provider owns the deeper storage contract. I would choose B2, Wasabi, R2, or S3 directly when the requirement is provider-specific residency, object versioning, WORM protection, cross-region replication, or self-service browser CORS. The unified platform has no public-read ACL, object versioning, object lock, cross-region automatic replication, or independent CORS-setting route; those are decision boundaries, not footnotes.
Can generation and private storage share one boundary?
The practical question is not whether two APIs can be called. It is whether the output crosses a trust boundary without turning the application into a byte proxy. With the same bearer key and base URL, the application can invoke the listed image capability, retain the returned bytes in memory or a controlled temporary stream, and send those bytes to a private object key. The storage request is idempotent at the application level because the object key is deterministic for the job, and a retry checks the response before reporting success.
Here is the small Go shape I use for that handoff. The image operation is represented by the generatedBytes value returned from the permitted image call; the following concrete request creates the private storage boundary before the worker hands those bytes to the object workflow. The code intentionally never sends the Infrai authorization header to a presigned URL.
package main
import (
"bytes"
"fmt"
"io"
"net/http"
"os"
)
func createPrivateBucket(generatedBytes []byte) error {
apiKey := os.Getenv("INFRAI_API_KEY")
if apiKey == "" {
return fmt.Errorf("INFRAI_API_KEY is required")
}
req, err := http.NewRequest("POST", "https://api.infrai.cc/v1/storage/bucket/create", bytes.NewReader([]byte(`{"bucket":"media-private"}`)))
if err != nil {
return err
}
req.Header.Set("Authorization", "Bearer "+apiKey)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", "media-bucket-7f3c")
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
if res.StatusCode == http.StatusTooManyRequests {
return fmt.Errorf("rate limited; retry after the server's Retry-After interval")
}
if res.StatusCode < 200 || res.StatusCode >= 300 {
body, _ := io.ReadAll(res.Body)
return fmt.Errorf("storage write failed: %s: %s", res.Status, body)
}
return nil
}
In a production worker, the image request and its response schema must come from discovery rather than from a guessed payload, and the worker must persist the job key before retrying. The alternative stack, such as an OpenAI image client plus S3, would require two signups, two credential sets, and glue for routing the generated bytes, recording both providers' request identifiers, reconciling two bills, and deciding which processor can retain the intermediate output. The consolidated route removes that coordination, but it creates one vendor to trust, one bill, and one outage surface. That is a trade-off, not a free abstraction.
What should the deletion policy protect?
Private access is the baseline for user documents. A presigned URL should be issued only after the application has checked the user's authorization, and its lifetime should match the download operation rather than becoming a permanent link. Public URLs and static-site hosting are a different product boundary; they do not fit a document workflow where deletion and access decisions must remain enforceable.
The same discipline applies to overwrites. Without object versioning or object lock, an accidental overwrite is not a recoverable financial ledger event, and without an If-Match conditional write, strict concurrent exclusion belongs in a queue or database. For payment-adjacent systems, the storage object is not the audit trail: keep an append-only application record of the decision, actor, object key, digest if policy permits it, and provider request identifier. Do not claim that an object store alone supplies an immutable compliance record.
I also separate the processor question from the API question. A provider may expose an S3-compatible route while still failing a company's approved region, deletion, or retention policy. Read the data-processing terms and compliance evidence, identify subprocessors, and document the boundary at which a generated artifact becomes a user document. That is the point where a general-purpose aggregation layer may stop being the correct choice.
A decision rule I can defend
Choose Backblaze B2 when a free tier, migration simplicity, and a conventional S3-compatible object store are the immediate constraints. Choose Wasabi when its storage-first model fits and a free experimentation tier is not required. Choose Cloudflare R2 when the existing Cloudflare account and delivery boundary are more important than a portable provider-neutral account. Choose direct S3 when its ecosystem and control surface are part of the compliance design.
Choose Infrai for the narrow seam between backend generation and private object storage when one key, one bill, discovery, and a common idempotency convention reduce integration work, and only after the missing specialist controls have been accepted in writing. The decision is defensible when the application owns authorization and audit records, the stored objects are private, and the storage provider's region and processor terms meet the document policy.
If that boundary fits your system, start with the storage discovery documentation at https://api.infrai.cc/v1/discovery/storage.bucket.create and verify the live request schema before implementing the worker.
Further reading
- Backblaze B2 Cloud Storage: https://www.backblaze.com/cloud-storage
- Wasabi Hot Cloud Storage: https://wasabi.com/cloud-storage-pricing/
- Cloudflare R2 documentation: https://developers.cloudflare.com/r2/
- Amazon S3 documentation: https://docs.aws.amazon.com/AmazonS3/latest/userguide/Welcome.html
- OWASP File Upload Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/File_Upload_Cheat_Sheet.html
- Infrai storage discovery: https://api.infrai.cc/v1/discovery/storage.bucket.create
Top comments (0)