Skip to content

RFC: x-defang-storage — managed object storage across clouds (Azure via S3-compat proxy, no static keys) #2212

Description

@defangdevs

Motivation

While building a Defang BYOC deploy bundle for Buzz (DefangLabs/buzz#2), we hit a gap: Buzz's deploy/compose/ bundle uses minio (S3-compatible, self-hosted) for object storage. There's no managed-storage extension for this today, so the Defang bundle had to be hand-written to take real S3-compatible endpoint/key config instead — asking users to provision a bucket and static credentials out-of-band, and only working cleanly on AWS/GCP. Azure has no native S3 API, so that bundle doesn't have a clean Azure story at all.

This is a generic gap, not Buzz-specific: any compose app that uses minio/S3 for storage hits the same wall. Proposing x-defang-storage (name TBD) to close it the same way x-defang-postgres/x-defang-redis closed it for databases/cache.

Scope note: there's a larger, prior design thread covering this same gap — defang-global PR 94 ("fold Fabric into the Portal repo"), section 4.3 of docs/plans/fold-fabric-into-portal.md — motivated by Fabric's own blob layer rather than a customer app. That plan is much bigger in scope (local/ephemeral dev engines, split-horizon DNS for presigned-URL portability, Fabric data-plane unification). This issue stays open as the narrower, Buzz-motivated slice: what's needed to deploy a real app's object storage on AWS/GCP/Azure today, without taking on the ephemeral-dev-environment or presigned-URL work. See that plan for the fuller design and the still-open decision on Azure (managed sidecar vs. documented v1 limitation) — Buzz is a second concrete motivating case for building the sidecar rather than shipping the limitation.

Proposal

A new extension, attached to a stub service the way x-defang-postgres/x-defang-redis attach to a postgres:/redis: service block (e.g. keep a minio:-shaped service in compose.yaml, tag it x-defang-storage: true). The minio image in that stub is a sentinel only — Defang elides it and never actually runs it, so MinIO Community Edition's reported archival (unmaintained, no security patches — flagged in the defang-global plan above) doesn't matter for this scope. Defang provisions real object storage per cloud and rewires the same env vars the app already expects — no compose.yaml rewrite needed per cloud.

Per-cloud plan

  • AWS: native S3 bucket. No static keys — scope an IAM policy to the bucket on the task's execution role, leave access-key/secret-key env vars blank. Apps using the AWS default credential chain (most S3 SDKs, including Buzz's rust-s3) pick this up automatically; this already works in Buzz's client today (Credentials::default() fallback in crates/buzz-media/src/storage.rs) but our current bundle doesn't use it — worth fixing there too.
  • GCP: native GCS bucket via its S3-compatible XML/interop API — no proxy needed, GCS already speaks S3 well enough. Auth is HMAC keys minted for a dedicated service account. Not fully keyless (HMAC secret still exists), but Defang-managed/rotated rather than user-managed, and tied to a real IAM identity we control.
  • Azure: no native S3 API, so this is the one cloud that needs a translation layer. Inject an S3-compatible proxy sidecar (e.g. s3proxy, which already has an Azure Blob backend) alongside the service — same pattern as the existing LiteLLM sidecar for LLM-provider normalization. The sidecar authenticates to Blob Storage via a Managed Identity on the Container App — genuinely keyless on that leg. The app-to-sidecar leg should trust network isolation (sidecar bound to localhost/private network only) rather than requiring real SigV4 keys between them, same as the LiteLLM sidecar likely already does.

Explicitly out of scope for this issue (deferred to the defang-global PR 94 plan, phase 2+):

  • Presigned URLs handed to third-party clients (browsers, etc.) — these break under a signature-less/localhost-trust sidecar, since SigV4 signs the Host header and the host is part of what's signed. Buzz doesn't need this: confirmed via grep -rn presign across buzz-media/buzz-relay that Buzz never issues presigned URLs — the relay proxies all media itself.
  • Split-horizon DNS / local ephemeral storage engine choice (SeaweedFS vs Garage vs Zenko CloudServer vs s3proxy-on-filesystem) — not needed when the app always talks to real cloud storage or the Azure sidecar, no local dev engine in play here.

Open questions

  • Final extension name/shape — x-defang-storage here vs. x-defang-object-storage in the defang-global plan. Should align on one.
  • Should Defang warn/refuse when it sees known NFS/S3-storage-unsafe patterns elsewhere in a compose file (e.g. a plain minio on a mounted volume), separately from this proposal?
  • s3proxy vs Zenko CloudServer for the Azure sidecar (both came up independently in this issue and the defang-global plan).
  • Companion work in pulumi-defang (provider/compose/types.go + per-cloud provider code) once this design is agreed — this issue is scoping the design first.

Reference

The Buzz PR (DefangLabs/buzz#2) is a concrete example of the workaround this would eliminate — with x-defang-storage, that bundle's README/compose.yaml wouldn't need to diverge from deploy/compose/ at all.

Metadata

Metadata

Assignees

No one assigned

    Labels

    byocBring Your Own CloudcomposeDocker Compose supportstorageRelating to data storage (redis, postgres, etc.)

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions