The local image request specs were green, so I thought uploads were done. The first real image I sent to production still returned HTTP 500.
The upload endpoint worked
Discourse accepted the authenticated multipart request and sanitized the image. The real exception in Logster was EACCES: the persistent volume root belonged to discourse, several nested directories belonged to root, and the web process could not write under /var/www/discourse/public/uploads/default/original/1X.
We fixed the volume instead of routing around it
Live images still stay on the Railway persistent volume, and private R2 still stores a full backup every 12 hours. Moving live uploads to R2 would only have hidden the permission fault.
We first repaired ownership on the existing directories. The permanent repair now runs before Discourse starts. It touches only ordinary files and directories under /shared/uploads that are not owned by discourse, follows no symlinks, and does not cross filesystems.
After deployment, the same source image uploaded successfully. Request specs can verify the application contract; a mounted production volume still needs its own check. Directory ownership is now an explicit startup constraint.
