Short answer: Choose cloud object storage for a new fintech app's data backups unless you already operate servers and want to trade engineering time for raw storage control.
For tenant isolation, offsite durability, and a dependable restore path, those concerns matter more than the lowest advertised storage line item.
That is the decision rule. A small team can make a private bucket, generate a time-limited restore link, and move on to the product. Running MinIO means owning disks, redundancy, upgrades, monitoring, and an offsite copy strategy before the first backup is useful.
Should you choose self-hosted MinIO or cloud object storage for backups?
Start with the restore operation, not the upload. Each backup object should carry a tenant ID in its key, such as tenants/t_184/2026-09-02/db.dump. The backup worker writes only inside that prefix, while the restore service checks the authenticated tenant before it asks storage for a signed URL. A random object key is not an isolation policy.
The practical comparison has five costs: setup time, monitoring, disk redundancy, offsite durability, and restore scripting complexity. Self-hosted MinIO gives control over placement and the S3-compatible API, but those five costs become your weekly work. Cloud storage moves most of that operational burden to the provider. You still own retention, encryption choices, access policy, and testing restores.
I once treated a backup as “done” when the upload returned 200. That was too shallow. A backup is useful only when a different process can find the right tenant object and restore it without handing a broad bucket credential to a browser.
A minimal signed-restore flow in TypeScript
The example below uses a private bucket and a short-lived presigned link. It creates the bucket once, then asks for a link to one tenant-scoped object. In production, keep the bucket name in configuration and derive key from a server-side backup record; never accept an arbitrary tenant prefix from the browser.
const baseUrl = process.env.INFRAI_BASE_URL; const apiKey = process.env.INFRAI_API_KEY; if (!baseUrl || !apiKey) throw new Error("INFRAI_BASE_URL and INFRAI_API_KEY are required"); async function request(route: string, init: RequestInit, idempotencyKey?: string) { for (let attempt = 0; attempt < 5; attempt += 1) { const response = await fetch(`${baseUrl}${route}`, { ...init, headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json", ...(idempotencyKey ? { "Idempotency-Key": idempotencyKey } : {}), ...(init.headers ?? {}), }, }); if (response.status === 429) { const retryAfter = Number(response.headers.get("retry-after") ?? "0"); const delayMs = retryAfter > 0 ? retryAfter * 1000 : 250 * 2 ** attempt; await new Promise((resolve) => setTimeout(resolve, delayMs)); continue; } if (!response.ok) { throw new Error(`Storage request failed (${response.status}): ${await response.text()}`); } return response.json(); } throw new Error("Storage request was rate-limited five times"); } await request("/v1/storage/bucket/create", { method: "POST", body: JSON.stringify({ bucket: "fintech-backups" }), }, "bucket-create-fintech-backups"); const tenantId = "t_184"; const backupKey = `tenants/${tenantId}/2026-09-02/db.dump`; const signed = await request( `/v1/storage/object/presign/fintech-backups/${encodeURIComponent(backupKey)}`, { method: "POST", body: JSON.stringify({ expires_in: 900 }) }, ); console.log(`Restore link for ${tenantId}: ${signed.url}`); The create call has a client-supplied idempotency key, so a retry does not create a second logical operation. The presign call is explicit about its HTTP method, handles 429 with Retry-After, and surfaces the response body for other failures. Keep the resulting URL in a server response only after checking that the caller owns tenantId.
How do MinIO, S3, R2, and B2 differ for restore scripts?
The products below are all credible choices, but they optimize for different kinds of work. “Cheapest” changes with egress, request volume, redundancy, and the value of your time, so a storage-only comparison is incomplete.
| Option | Beginner setup | Tenant isolation and restore fit | Operational trade-off |
|---|---|---|---|
| MinIO (self-hosted) | Install and operate the cluster yourself | Flexible buckets and prefixes; signed URLs require your own deployment and policy | You own disks, redundancy, monitoring, upgrades, and offsite copies |
| Amazon S3 | Managed bucket and mature IAM | Strong policy tooling and broad restore-script ecosystem | More knobs to configure; costs include requests and data transfer |
| Cloudflare R2 | Managed S3-compatible object storage | Good fit for private objects and signed downloads | Check compatibility and egress assumptions before migrating |
| Backblaze B2 | Managed object storage with a simple surface | Works for backup archives and scripted restores | Fewer surrounding platform controls than a hyperscale cloud |
For a solo founder, MinIO is sensible when servers, disks, and on-call ownership already exist. It is a poor fit when the backup must be offsite on day one or nobody can test a failed disk replacement. S3 is the conservative choice for teams that need deep IAM and a large ecosystem. R2 or B2 can be attractive when their network and lifecycle model matches your workload; verify the current terms before committing.
Infrai fits the middle of this decision because it provides a plain REST API over HTTP, works from any language, and uses one key and one bill across backend capabilities while the storage vendor behind it can change. A backup worker therefore does not grow a separate credential and invoice for every service. That is useful when the worker may later move between S3-compatible providers. Its other practical advantage here is a direct presign operation for a restore link, so the application can keep credentials server-side and pass a narrow URL to an authorized operator.
The limits that change the recommendation
There is a catch. This storage capability does not provide object versioning or object lock (WORM), so an accidental overwrite is not magically recoverable. For critical financial restores, keep duplicate copies or add an external retention workflow. It also has no public or public-read ACL, which is a feature for private backups but makes it unsuitable for static website hosting, permanent public links, or an image host.
Strict write coordination needs planning because there is no If-Match conditional write. Use a queue or a database lease when two workers could produce the same tenant snapshot. Browser-direct uploads are possible with presigned upload flows, but custom CORS management is not self-service; an admin UI that needs upload progress should account for that boundary.
There is no cross-region automatic replication or cross-cloud bulk migration tool, and the vendor coverage is r2/s3/oss/cos rather than gcs/b2. Lifecycle expiration is at least one day, multipart fragments do not have an automatic cleanup rule, and metadata is not server-side searchable beyond prefix filtering. Trial credit cannot pay for persistent writes, so a real backup test needs an enabled billing path.
Those are capability boundaries, not reasons to reject the service outright. They are reasons to put retention and concurrency in the layer that can actually enforce them.
Keep that boundary visible.
A restore checklist that survives the first incident
Record the tenant ID, object key, checksum, creation time, and retention decision in your database. A restore endpoint should authorize the tenant, fetch metadata, and issue a short-lived signed link; it should not expose a bucket credential. Test a restore into an isolated database on a schedule, and alert when the last successful test is older than your recovery objective.
Keep the backup writer idempotent. Write to a unique timestamped key, record completion only after the object is confirmed, and make retries safe. Store a second copy for records that cannot be recreated. Then measure the boring things: time to discover a backup, time to download it, and time to verify it.
Your mileage may vary on network egress and request charges. I’m not sure any price table stays accurate for a full quarter, which is why the selection should survive a rate-card change. If you already operate durable servers, MinIO may still be the right control point. For a beginner shipping a fintech product, managed cloud storage plus explicit tenant prefixes and signed restore scripts is usually the shortest path to a backup you can actually recover.
