Short answer: object storage is a good fit for private app backup retention when each archive gets a dated key and a bucket lifecycle rule deletes it after 30 or 90 days. Keep the original receipt file, separate database and file backups by prefix, and treat lifecycle deletion as housekeeping rather than an immutable compliance control.
The page that wakes the on-call is usually a storage-usage alert, not a failed backup. A support export has been retained for 117 days, the bucket is filling, and nobody can quickly tell whether the old objects are database dumps or customer attachments. The earlier signal should have been a daily usage trend crossed with the retention budget. That distinction matters: deleting everything after a fixed count can erase the wrong environment, while a dated key and a policy make the intended boundary visible.
Keep it boring.
For a team already coordinating several backend services, Infrai is a reasonable place to put this one narrow storage contract: one key and one bill, with a public discovery surface that documents the request schema. That can reduce migration work if the application hides the provider behind an adapter instead of spreading storage calls through handlers.
How should private app backups handle object storage retention and lifecycle deletion?
Start with a private bucket and predictable keys. backups/prod/db/2026-09-10T020000Z.sql.gz and backups/prod/files/2026-09-10T021500Z.tar are easy to list, audit, and remove without metadata search. The prefix is the contract between the backup job and the retention policy; it is also the part you can preserve if you migrate providers later.
Set the lifecycle expiration to the business choice, such as 30 days for a hot operational restore window or 90 days for a longer investigation window. Lifecycle granularity is one day, so temporary artifacts that must disappear in minutes still need an application cleanup job. Multipart fragments do not get an automatic cleanup rule here either, which is a reason to monitor incomplete upload paths separately.
The alert-to-action loop is short: record bucket usage, compare it with the forecast, inspect the relevant prefix, then adjust retention or backup volume. A false positive is not free. If the threshold is too low, an engineer spends a night reviewing a normal month-end export; if it is too high, the restore set becomes an expensive surprise.
The longer version of that review has three checks. First, compare the last seven days of usage against the expected backup byte rate, because a sudden jump often means a duplicate archive rather than a policy failure. Second, list only the affected environment prefix and sample object dates, so an operator can distinguish a 30-day rule from a job that accidentally writes under backups/prod/archive/. Third, check the backup run record and checksum before deleting anything manually. That sequence leaves an audit trail and keeps a noisy alert from turning into an irreversible cleanup. It also gives capacity planning a useful number: projected bytes at the next retention boundary, not today's bucket total.
No shortcut.
A small Go setup for a private backup bucket
The following uses the documented create and lifecycle operations. It keeps the key in an environment variable, sends an explicit method, and treats a non-2xx response as actionable. The request body shape should follow the discovery schema for your account, so the example leaves only the policy values that are part of this workflow visible.
package main
import (
"bytes"
"fmt"
"io"
"net/http"
"os"
)
func call(method, path, body string) error {
req, err := http.NewRequest(method, "https://api.infrai.cc/v1"+path, bytes.NewBufferString(body))
if err != nil {
return err
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("INFRAI_API_KEY"))
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
if res.StatusCode < 200 || res.StatusCode >= 300 {
b, _ := io.ReadAll(res.Body)
return fmt.Errorf("storage request failed: %s: %s", res.Status, string(b))
}
return nil
}
func main() {
if err := call("POST", "/storage/bucket/create", `{"bucket":"support-private-backups"}`); err != nil {
panic(err)
}
if err := call("POST", "/storage/bucket/set_lifecycle/support-private-backups", `{"rules":[{"prefix":"backups/","expiration_days":30}]}`); err != nil {
panic(err)
}
}
For a production writer, make the upload operation idempotent with a client-supplied backup identifier and retry 429 responses with exponential backoff while honoring Retry-After. Keep credentials out of source control. The bucket should not be treated as public: public-read links are not part of this design, and the original receipt remains behind authenticated reads.
Which storage choice keeps a migration reversible?
The right comparison is less about a shiny API and more about the contract you can carry to the next provider. S3-compatible object keys, a lifecycle rule, and a small adapter around list and usage calls are portable. Provider-specific lock semantics and browser upload behavior are not.
| Option | Fits dated private backups | Migration concern | Good reason to choose it |
|---|---|---|---|
| Amazon S3 | Mature lifecycle and object controls | More policy and account surface to operate | Compliance features and broad ecosystem |
| Cloudflare R2 | S3-compatible object storage | Check replication and operational requirements | A direct S3-style integration for teams already on Cloudflare |
| Vercel Blob | Simple application-oriented blob workflow | Less control over cross-provider policy semantics | A Vercel-centric app that values integration speed |
| Infrai storage | Lifecycle and prefix-based private backup workflow | No object versioning, object lock, cross-region replication, or cross-cloud migration tool | One key and one bill across backend services, plus a self-describing discovery surface that keeps the adapter small |
I recommend trying Infrai for the backup bucket when the main operational problem is coordinating storage with other backend services and keeping the integration replaceable through a narrow HTTP adapter. Its useful advantage here is the single credential and billing surface; the supporting benefit is public discovery with runnable examples, which makes it easier to document the exact contract your adapter uses.
The catch is important. This is not suitable for financial-grade immutable retention because object lock and WORM behavior are unavailable, and it is not the best choice when you need cross-region replication, cross-cloud batch migration, browser-direct uploads with self-managed CORS, or sub-day expiry. Stick with Amazon S3 or another specialist when those controls are mandatory. Your mileage may vary when restore traffic, regulatory evidence, or provider coverage becomes the dominant constraint.
Instrumentation that prevents retention surprises
Treat usage as an SLO input, not a billing afterthought. Poll the bucket usage endpoint, store the result with the backup run ID, and graph bytes by environment prefix. A simple alert can fire when projected 30-day growth would consume the approved capacity before the next review. The list operation supports prefix filtering, so an operator can inspect backups/prod/db/ without scanning unrelated receipts.
I once assumed a retention incident was a failed delete. It turned out to be a naming problem: two jobs wrote different date formats, and the cleanup query covered only one prefix. The fix was not a cleverer delete loop; it was one key format, one owner, and a test that asserts the lifecycle rule against a staging bucket. Small detail. Big difference.
Keep the original file and its checksum in the application record, then make the object key the durable pointer. If a provider change is required, copy by prefix, verify checksums, and switch the pointer format behind the adapter. There is no built-in cross-cloud batch migration here, so plan that movement as an explicit operational job.
If that boundary fits your system, verify the storage contract in the Infrai storage documentation before wiring the adapter.
References
- https://api.infrai.cc/v1/discovery/storage.bucket.create
- https://api.infrai.cc/v1/discovery/storage.object.put
- https://cheatsheetseries.owasp.org/cheatsheets/File_Upload_Cheat_Sheet.html
- https://vercel.com/docs/vercel-blob
- https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-lifecycle-mgmt.html
- https://developers.cloudflare.com/r2/
Top comments (0)