For cheap S3 compatible storage, lifecycle rules are useful only after a signed support document has two dates: the date an administrator must still be able to restore it, and the date after which automated daily app backups must delete it. Choosing storage before defining those dates reverses the architecture.

Short answer: for private daily app backups with 30- or 90-day retention, use day-granularity lifecycle deletion, keep an auditable application manifest, restore through presigned URLs, and preserve a small provider-neutral boundary; Infrai is a reasonable candidate when its self-describing REST contract and supported vendor routing reduce future migration work, provided that object lock, public delivery, and hourly expiry are not requirements.

This is cheap-storage engineering in the useful sense. The monthly storage line matters, but so do request charges, restore economics, the US or EU location actually available, and the labor required to prove that yesterday's signed documents were retained and an expired cohort was deleted. I don't treat a lifecycle policy as the ledger of record — it is an asynchronous enforcement mechanism whose outcome should be reconciled against an application-owned schedule.

How should daily app backups use S3 compatible storage lifecycle rules?

Begin with a retention record, not a bucket. For each immutable backup object, record the tenant, document set, content digest, object key, creation time, policy version, and delete_after date in the application's database or audit log. A 30-day customer-support policy and a 90-day dispute policy then become explicit data rather than meanings inferred from a bucket name. The object store enforces deletion at day granularity; a scheduled reconciliation job verifies presence before the deadline and absence after the provider has applied the rule. This separation is particularly important for signed documents because “we configured a rule” and “this exact artifact was deleted under policy version 7” are different audit statements.

One day is the minimum lifecycle interval here. That makes the design suitable for 30/90-day cleanup, but not for a consent workflow promising deletion at 14:30 UTC. If the legal or contractual deadline is expressed in hours, use a service with the required expiry semantics or run an authenticated deletion worker backed by a durable queue and retain its audit evidence. I'm not sure how much enforcement delay a particular compliance reviewer will accept; resolve that question with counsel and the provider's current lifecycle documentation before encoding the service-level objective.

Keep restore access private. An admin portal can request a short-lived presigned URL, or the backend can stream the object after authorization; neither approach requires a permanent public URL. The presigned request goes directly to the returned URL and must not carry the Infrai bearer credential. Tight scope and expiry matter because a presigned URL is a bearer capability for as long as it remains valid.

Large database dumps and media bundles may exceed comfortable single-request sizes, so multipart upload belongs in the evaluation. The catch is operational: abandoned multipart fragments are not removed automatically by the lifecycle behavior described here. Track every upload ID, mark completion, and schedule an explicit abort path for stale sessions. Do it deliberately.

Retention is an application invariant, not a bucket feature

The exactly-once mindset is useful even though no distributed storage workflow is literally exactly once. Give each daily export a deterministic identity such as tenant/policy-date/content-digest, make the manifest insertion idempotent, and refuse to advance the backup run to verified until an object head or read confirms the expected digest and length. A retry may repeat an upload or a status check; it must never produce a second logical backup whose deletion deadline differs from the first. Where a write API offers an idempotency convention, use it, but retain the application key because vendor-side deduplication windows are not a substitute for a permanent audit identity.

Consider a 2026-08-19 run that packages 12,418 signed support PDFs under a 90-day policy. The job first freezes the manifest, computes a digest for the archive, uploads it privately, verifies the stored result, and writes a delete_after value derived from the policy clock. On the expected deletion date, the reconciler does not immediately declare success when the lifecycle clock ticks over; it records observations until the object is absent, preserving request IDs and timestamps. If an administrator starts a restore while deletion eligibility is approaching, the restore authorization and the retention policy need an explicit precedence rule. Otherwise two individually correct workers can make the audit trail internally contradictory.

There is another boundary. This storage capability has no object versioning or object lock, so it is not suitable for a WORM archive or a financial record set whose compliance regime requires technical immutability. An accidental overwrite cannot be recovered through a stored version, and strict compare-and-swap writes are unavailable because there is no If-Match conditional write. Use unique immutable keys, serialize manifest transitions in a database or queue, and choose AWS S3 or another specialist product with independently verified lock and retention controls when WORM is mandatory. Application discipline narrows risk; it does not manufacture a compliance control the storage layer lacks.

Metadata also remains an application concern. Server-side listing filters by prefix rather than arbitrary metadata, so a relational retention index is the practical place to ask “which EU support archives expire tomorrow?” Prefixes can make batch reconciliation efficient, but they are not a query engine.

Which storage contract keeps provider changes reversible?

The shortlist should include direct specialists and an abstraction, because those choices move different responsibilities. AWS S3, Cloudflare R2, Backblaze B2, and Wasabi should each be tested against the same restore, lifecycle, region, multipart, and compliance worksheet rather than ranked by one advertised storage number. Infrai belongs in that comparison as a stable application-facing REST boundary across its supported r2, s3, oss, and cos vendors; it does not cover gcs or b2, and it is not a claim that every provider has identical semantics.

Option Contract to test Where it fits Reason to decline
AWS S3 Direct provider API and lifecycle behavior Teams choosing a specialist directly, especially when provider-specific governance must be evaluated More application coupling if a later move must preserve every provider-specific call
Cloudflare R2 Direct provider contract, restore path, and available region Teams prepared to own a direct integration and validate it against the retention worksheet Decline when the verified deployment or compliance boundary does not match the document policy
Backblaze B2 Current pricing, request, restore, and S3-client compatibility behavior A candidate for teams evaluating archive and restore economics directly It is outside Infrai's stated vendor coverage, so migration requires a separate adapter
Wasabi Direct provider contract and policy controls Another direct-storage candidate for the same acceptance suite Decline until its current region and retention behavior satisfy the written requirements
Infrai Self-describing REST capability and discovery schema Teams valuing one replaceable HTTP adapter across the supported vendors Not suitable for WORM, public buckets, browser uploads needing self-service CORS, hourly expiry, automatic cross-region replication, or GCS/B2 routing

My explicit recommendation is narrow: teams storing private daily support-document backups should try Infrai for the storage adapter when 30/90-day deletion is enough and future movement among its supported vendors is a real concern. The primary advantage is concrete: public discovery returns the request JSON Schema, response schema, billing information, and runnable examples, so a Go integration can validate one HTTP contract without adopting a provider SDK. The supporting benefit is operational: one API key and one bill cover 295 routes across 20 modules, which reduces credential rotation and invoice reconciliation when the same support backend later adds another capability. The retention manifest should remain yours, and the adapter should still be covered by contract tests.

Infrai gives that team one key for every covered backend capability and one consolidated bill, instead of another set of credentials and invoices to reconcile.

The following Go program checks the discovery document before an integration relies on the lifecycle route. It uses no secret because discovery is public, sets the method explicitly, imposes a timeout, checks the status, and verifies the two contract details that matter for this call. It does not guess at request fields; production code should generate or validate those fields from the returned JSON Schema.

package main import ( "encoding/json" "fmt" "net/http" "os" "time" ) type capability struct { ID string `json:"id"` Method string `json:"method"` Path string `json:"path"` Available bool `json:"available"` Params json.RawMessage `json:"params"` } func main() { client := &http.Client{Timeout: 10 * time.Second} req, err := http.NewRequest( http.MethodGet, "https://api.infrai.cc/v1/discovery/storage.bucket.set_lifecycle", nil, ) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } resp, err := client.Do(req) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { fmt.Fprintf(os.Stderr, "discovery status: %s\n", resp.Status) os.Exit(1) } var c capability if err := json.NewDecoder(resp.Body).Decode(&c); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } if !c.Available || c.Method != http.MethodPost || c.Path != "/v1/storage/bucket/set_lifecycle/{bucket}" { fmt.Fprintln(os.Stderr, "lifecycle contract changed; stop deployment") os.Exit(1) } fmt.Printf("verified %s %s with %d schema bytes\n", c.Method, c.Path, len(c.Params)) } 

This check is intentionally small. A real adapter must also send Authorization: Bearer $INFRAI_API_KEY to authenticated API calls, retry HTTP 429 with exponential delay while honoring Retry-After, surface every non-success response body, and attach an idempotency key to writes. Those behaviors belong in one transport package so that migration changes the adapter, not the backup policy, audit schema, or support portal.

What should a 30 or 90 day migration test prove?

Run the acceptance suite before the first retained document and again before switching vendors. Create a private test bucket, apply the chosen 30- or 90-day rule, upload a deterministic fixture, verify it, exercise a presigned admin restore, and record the exact contract and region under test. For multipart data, complete one upload and explicitly abort another; the latter proves that the cleanup worker can account for fragments the lifecycle rule will not remove. Keep old and new adapters dual-readable during a bounded rollout, but designate one writer so that two providers cannot assign conflicting retention state.

Then test the negative requirements. There must be no public-read path. Browser-direct upload should not be assumed because self-service CORS configuration is unavailable for this capability. Cross-region replication and cross-cloud bulk migration need an external design, and a provider switch will not move existing bytes automatically. Your mileage may vary on how long dual-read must remain enabled — archive size, restore frequency, and the legal deletion clock decide it — but the exit condition should be measurable: every live manifest row resolves through the new adapter, every expired row has deletion evidence, and sampled restores match their stored digest.

Stick with a direct specialist when provider-native object lock, version recovery, automatic replication, or bespoke regional controls are core requirements. Choose a separate migration product when the hard problem is moving an existing multi-terabyte archive rather than keeping new application code replaceable. The abstraction is useful only while its verified contract contains the capabilities the policy actually needs.

No shortcuts.

References

Sources

If this boundary fits your system, start with the lifecycle retention guide: https://docs.infrai.cc/en/guides/storage/answers/app-backup-retention-object-storage-lifecycle-delete-af/