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:
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.