Skip to content

ADR 0002: S3-compatible product media

  • Status: accepted for the MVP architecture
  • Date: 2026-08-25

Context

Product images must survive application-container replacement and must be uploadable through Medusa Admin. Medusa's local file provider writes into the application filesystem and is documented for development only. The VPS is a single failure domain, so the MVP needs durable storage and verified backups, not an illusion of high availability.

The storefront and Medusa also have different media concerns. Medusa writes by using authenticated S3 API credentials and an internal endpoint. Browsers read public product assets from a stable HTTPS origin that contains no credentials.

Decision

Use Medusa's S3 File Module Provider in every environment. Use the pinned Garage service as the S3-compatible implementation for local development and the single-node VPS MVP, subject to the VPS resource audit and a tested off-VPS backup before launch.

The same Medusa configuration is used locally and in production:

Setting Local development VPS production target
S3_ENDPOINT http://garage:3900 http://garage:3900 on the private Compose network
S3_FILE_URL http://andreaweb-media.web.garage.localhost:3902 https://media.andreazambrano.co
S3_BUCKET andreaweb-media andreaweb-media
storefront protocol http https
storefront hostname andreaweb-media.web.garage.localhost media.andreazambrano.co

S3_ENDPOINT is the authenticated write/control path used by Medusa. It stays private. S3_FILE_URL is the browser-visible base URL recorded on uploaded files. It is deliberately independent of the internal endpoint.

On the VPS, system Nginx will terminate TLS for media.andreazambrano.co and proxy only public reads to Garage's web endpoint on loopback. Because the Garage bucket website uses virtual-host routing, that proxy must send the internal Host value andreaweb-media.web.garage.localhost. The S3 API and Garage Admin API must not be internet-accessible.

The storefront accepts the media origin through provider-neutral STOREFRONT_MEDIA_PROTOCOL, STOREFRONT_MEDIA_HOSTNAME, and STOREFRONT_MEDIA_PATHNAME settings. Moving later to hosted S3-compatible storage or a CDN changes deployment configuration rather than storefront code.

Persistence and recovery

Garage metadata and object data live in separate durable volumes. They must be backed up as one coordinated storage system, copied off the VPS, and restored in a rehearsal before public launch. PostgreSQL also contains product records with the returned media URLs, so database and object-storage recovery procedures must be tested together.

Public product media is intentionally world-readable. Credentials, exports, private documents, and customer data must never be placed in this public bucket.

The stable public media hostname matters because Medusa stores the returned URL with the product image. Changing domains later is possible, but existing image records may require a controlled URL migration or continued redirect from the old hostname.

Verification

Run:

npm run media:verify

The check uploads a temporary object through Medusa's File Module, reads it from the public URL, restarts Garage, reads the same bytes again, and deletes the temporary object. This exercises the same provider interface used by Admin and does not modify catalog media.

The production acceptance test must repeat upload, public HTTPS read, container replacement, second read, deletion, backup, and restore using the deployed hostname and production volumes.

Consequences

  • Application images and containers remain stateless and replaceable.
  • Development exercises the production class of provider instead of a local filesystem substitute.
  • One-node Garage is not highly available; loss of the VPS can still remove the live copy, so off-VPS backups are mandatory.
  • A future hosted object store or CDN remains possible without changing the Medusa product model.