You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Public files don't render in Docker Compose, and 'private' isn't enforced by the bucket in Aspire #1410
Follow-up from #1402. Uploads now work in the Docker Compose stack, but files uploaded as Public (for example the admin avatar) still don't render there. BuildPublicUrl returns PublicBaseUrl, or else ServiceUrl (http://rustfs:9000/fsh/...). Browsers can't resolve that host, and the compose bucket has no anonymous-read policy anyway.
Why this isn't a one-line fix
Visibility is not encoded in the storage key. StorageKeyBuilder puts every file under the same layout, so there's no way to grant anonymous read to public files only:
Stack
Bucket policy today
Effect
Aspire (AppHost.cs)
anonymous s3:GetObject on fsh/*
Public files render. Private files are also readable without auth by anyone who has the key. Keys contain a UUIDv7, so they're hard to guess, but "private" isn't enforced by the store.
Compose
none
Private files are safe, public URLs are broken
AWS Terraform
off by default; app_s3_public_read_prefix (uploads/) if enabled
Can only work if public files live under a known prefix, which they don't
Proposal
Put visibility in the key. Have StorageKeyBuilder add a public/ or private/ root prefix (existing keys stay valid; only new uploads get it).
Grant anonymous read only on public/* in all three stacks: the Aspire bootstrap policy, a new compose bootstrap policy, and the Terraform app_s3_public_read_prefix default changed to public/.
Compose: set Storage__S3__PublicBaseUrl=${FSH_S3_PUBLIC_URL}/fsh.
Changing visibility (if a file can go from public to private) becomes a copy plus delete. We need to check whether the Files module allows that today.
Alternative considered: an API endpoint that answers 302 with a presigned GET for public files. It needs no bucket policy anywhere, but it adds an API round trip per image and URLs change on every request, so browsers and CDNs can't cache them. Prefix + policy is better for public assets, since those are what you want cached.
Signed off on the design: visibility goes in the key, and anonymous read is granted on public/* only, in all three stacks. I'm implementing it now. Decisions:
Key layout. New uploads go under public/ or private/, and the storage key builder stays the one place that decides.
Existing files. Narrowing the Aspire policy from fsh/* to public/* would break every public file uploaded before the change. The PR ships a one-time migration that copies existing public objects to public/… keys and updates their rows, so nothing is lost on upgrade. Existing private objects stay where they are and become non-public once the broad policy goes.
Visibility change. If the Files module allows changing a file's visibility, that becomes copy + delete under the new prefix.
Compose. Adds a bootstrap policy for public/* and sets Storage__S3__PublicBaseUrl=${FSH_S3_PUBLIC_URL}/fsh. The README gets a note to firewall the published S3 port, or bind it to 127.0.0.1 when the proxy runs on the same host.
Terraform. The app_s3_public_read_prefix default becomes public/.
Activity
iammukeshm commentedon Sep 30, 2026
Signed off on the design: visibility goes in the key, and anonymous read is granted on
public/*only, in all three stacks. I'm implementing it now. Decisions:public/orprivate/, and the storage key builder stays the one place that decides.fsh/*topublic/*would break every public file uploaded before the change. The PR ships a one-time migration that copies existing public objects topublic/…keys and updates their rows, so nothing is lost on upgrade. Existing private objects stay where they are and become non-public once the broad policy goes.public/*and setsStorage__S3__PublicBaseUrl=${FSH_S3_PUBLIC_URL}/fsh. The README gets a note to firewall the published S3 port, or bind it to127.0.0.1when the proxy runs on the same host.app_s3_public_read_prefixdefault becomespublic/.fix(compose): serve public files from the public/* prefix and documen…
fix(files): serialize storage moves per file and make the old-object …
feat(storage): put file visibility in the key and open only public/* …
iammukeshm commentedon Sep 30, 2026
Fixed in #1422 (docs: fullstackhero/docs#262). Follow-ups are tracked in #1423.