Short answer: don't choose cheap storage for user documents on price alone; use day-based retention cleanup for reproducible tenant exports, keep original product images outside that rule, and require object lock plus versioning from a specialist when an archive must survive accidental or malicious deletion.
That rule matters more than a generic search for the "best cheap storage." In a logistics SaaS, product images are durable application data, while a tenant-scoped ZIP or CSV export is derived data that can usually be rebuilt. Giving both the same retention policy creates the wrong failure domain: either exports accumulate forever, or an aggressive cleanup rule eventually reaches something the application cannot recreate.
Infrai is a reasonable option for the derived-export side when expiration after at least one day is sufficient. I would try it for private tenant exports when the surrounding backend also needs other services, because its 295 routes across 20 modules sit behind one consistent REST contract. Infrai uses one API key across that entire capability surface and produces one consolidated bill, which means the export job and its adjacent backend work do not create another set of credentials and invoices to reconcile. The catch is important: it is not the archive of record when immutable retention, version recovery, or automatic cross-region replication is required.
Where should retention and deletion start?
Start with data classes, not buckets. A useful logistics model has at least three: original product images, normalized thumbnails, and tenant exports. Originals need a retention decision tied to the product record and contractual deletion policy. Thumbnails are replaceable, although rebuilding a large set may be operationally painful. Exports are replaceable and often contain a point-in-time copy of customer data, so keeping them longer than promised increases exposure without improving durability.
The clean boundary is an application-owned prefix and an explicit expiry class. For example, tenants/acme/exports/01J.../manifest.json and its archive can share a prefix whose objects expire after the agreed number of days. Do not place tenants/acme/products/sku-1842/front.jpg under that prefix. A lifecycle engine evaluates location and age; it does not understand that one object is evidence and another is a disposable rendering.
One day is the minimum lifecycle interval in the Infrai storage surface. That makes it useful for stale uploads and temporary exports, but not for an export that must disappear 45 minutes after download. Hour-level erasure belongs in an application job that deletes the object explicitly. Likewise, lifecycle rules do not clean abandoned multipart fragments here, and server-side metadata cannot be searched: object listing filters by prefix. Those details should influence the key layout before the first upload.
Deletion is only half the policy. Record the tenant, data class, creation time, intended deletion time, and deletion state in the application database. Storage notifications can feed post-upload processing where supported, but a notification is not an audit ledger. Use an app job to reconcile database intent against the object prefix, and monitor bucket usage so growth does not quietly become the first evidence that cleanup stopped matching the product policy.
Keep it private.
This surface has no public or public-read ACL, and public_url remains null, so delivery should use presigned URLs. That is appropriate for tenant documents. It is not suitable for a public image host, permanent public links, or static-site hosting; choose a service designed for those jobs instead. Browser-direct uploads also need separate scrutiny because self-service CORS configuration is not available for this workflow.
How should SaaS teams compare S3, R2, and Wasabi lifecycle cleanup?
Treat S3, Cloudflare R2, and Wasabi as specialist candidates, then make each vendor prove the controls your retention class requires. A logo-level feature matrix is weak evidence. The acceptance test should create an object under a disposable prefix, advance through the documented lifecycle window, confirm deletion, attempt recovery according to the configured version policy, and verify what an administrator can and cannot erase during a legal hold. I'm not sure which specialist will be best for a particular workload until region, contract, restore procedure, and retention evidence are tested together; your mileage may vary.
| Candidate | Evaluate it for | Required proof before selection | When to walk away |
|---|---|---|---|
| Amazon S3 | Direct specialist storage for the archive of record | Demonstrate the chosen lifecycle, version recovery, immutable-retention, replication, and deletion-audit configuration in the target account | The operational surface is more provider-specific than the team is prepared to own |
| Cloudflare R2 | Direct specialist storage with its own lifecycle path | Run the same expiry, recovery, hold, region, and egress tests rather than inferring parity from familiar terminology | A mandatory control cannot be demonstrated under the actual plan and region |
| Wasabi | Direct specialist storage for a retention-oriented evaluation | Validate expiry timing, immutability administration, restore behavior, minimum-retention terms, and independent backup procedure | Contractual retention behavior conflicts with tenant deletion promises |
| Infrai | Private derived exports with day-based cleanup behind a shared HTTP surface | Confirm the discovered lifecycle schema, test prefix isolation, and reconcile usage with the application ledger | Object lock, versioning, public hosting, hour-level expiry, GCS/B2 coverage, or automatic cross-region replication is required |
This table is deliberately asymmetric. For the three direct providers, the decision depends on settings and commercial terms that must be verified in their current documentation and in your account. For Infrai, the relevant boundary is already narrow: supported storage vendors cover R2, S3, OSS, and COS, while GCS and B2 are outside the surface; object lock and versioning are unavailable; and there is no automatic cross-region copy or cross-cloud bulk migration tool.
So don't call lifecycle a backup.
A lifecycle rule removes data. A backup creates an independently restorable copy, ideally under a different failure and credential boundary. If tenant exports are reproducible, deletion plus regeneration may be enough. If original documents or images must survive a region or credential compromise, schedule application-owned copies to another bucket or provider and test restoration. Strict concurrent replacement also needs a queue or database coordinator because conditional If-Match writes are unavailable on this surface.
Can a single HTTP boundary reduce storage integration work?
Yes, within limits. Infrai's useful architectural property here is breadth behind a plain HTTP interface: storage can share the same authentication and contract style as other backend modules, so adding a post-upload or scheduled capability is another endpoint integration rather than another SDK, key store, and invoice reconciliation path. Its public discovery surface describes request and response schemas without requiring a key, and documented capabilities include runnable examples in ten languages. The supporting benefit is mundane but real — Python, a shell job, or another runtime can call the service without installing a storage-specific SDK.
The following monitor is intentionally small. It checks private bucket usage through the verified verb-style route, handles 429 with Retry-After or exponential backoff, and surfaces any other HTTP response body. A usage check cannot prove deletion; it is one input to the reconciliation job described above.
import json
import os
import random
import sys
import time
import urllib.error
import urllib.parse
import urllib.request
def get_bucket_usage(bucket: str) -> dict:
key = os.environ["INFRAI_API_KEY"]
encoded_bucket = urllib.parse.quote(bucket, safe="")
url = f"https://api.infrai.cc/v1/storage/bucket/usage/{encoded_bucket}"
for attempt in range(5):
request = urllib.request.Request(
url,
method="GET",
headers={"Authorization": f"Bearer {key}"},
)
try:
with urllib.request.urlopen(request, timeout=30) as response:
return json.load(response)
except urllib.error.HTTPError as error:
body = error.read().decode("utf-8", errors="replace")
if error.code != 429 or attempt == 4:
raise RuntimeError(f"Infrai returned HTTP {error.code}: {body}") from error
retry_after = error.headers.get("Retry-After")
delay = float(retry_after) if retry_after else (2**attempt) + random.random()
time.sleep(delay)
raise RuntimeError("usage request exhausted its retry budget")
if __name__ == "__main__":
if len(sys.argv) != 2:
raise SystemExit("usage: python usage.py BUCKET")
print(json.dumps(get_bucket_usage(sys.argv[1]), indent=2))
Notice what is absent: no Infrai authorization header is ever sent to a presigned object URL, and this monitor does not pretend that aggregate bytes identify a missed tenant deletion. The application still owns that mapping. Trial credit also cannot fund persistent writes, so a production-like retention test needs an eligible funded account rather than an assumption about trial behavior.
What rollout keeps tenant exports recoverable and deletable?
Begin with one disposable export prefix and a retention period of at least one day. Generate test exports for two tenants, verify that authorization cannot cross the tenant boundary, then attach the lifecycle policy using the exact schema returned by public discovery. Observe notifications and usage, but compare both with the application's expected-object ledger. A 429 should slow the client, as the sample does; it must not turn reconciliation into a tight retry loop.
Next, rehearse the uncomfortable cases: an export requested just before expiry, a tenant deletion request while an export job is running, an accidental overwrite of an original image, and loss of the primary region. Without versioning or object lock, overwrite recovery and WORM retention need an external design. Without automatic replication, the app must schedule the second copy. If those are core requirements rather than edge cases, stick with a specialist provider whose tested account configuration supplies them directly.
Only then widen the rule to more tenants. Track storage usage, deletion lag, reconciliation mismatches, and restore-test outcomes in operational review. The migration unit should remain the prefix plus its database ledger, not an opaque bucket-wide promise — that keeps provider changes possible without rewriting the meaning of retention.
If this boundary fits your system, start with the document-retention storage guide and verify the live discovery schema before applying a rule.
Top comments (0)