Media storage ​
Uploaded files, avatars, banners, custom emoji and sounds are stored either on the server's disk or in S3-compatible object storage. Pick one in the media: block of app.config.yaml.
All media is served through the API, never straight from storage. Message attachments need a signed-in member who can see the conversation. Avatars and other public images are served without signing in. Either way the bucket or folder itself stays private.
On disk ​
yaml
media:
storage: local
local:
dataDir: /dataFiles are written to <dataDir>/uploads/. In the Docker setup, /data is docker/data/refract/ on the host, so uploads survive container rebuilds and are part of your backups. Anything outside /data is lost when the container is recreated.
S3 and S3-compatible storage ​
yaml
media:
storage: s3
s3:
bucket: my-refract-media
region: ap-southeast-2
keyPrefix: uploads/
publicBaseUrl: https://my-refract-media.s3.ap-southeast-2.amazonaws.com
# endpoint: https://minio.example.com # MinIO or another S3-compatible API
# forcePathStyle: true # if the provider wants path-style bucket URLs- Credentials come from the environment, never the config file:
AWS_ACCESS_KEY_IDandAWS_SECRET_ACCESS_KEY(andAWS_SESSION_TOKENfor temporary ones), or the host's IAM role. The server refuses to start if the config file contains keys. publicBaseUrlis required: the bucket's own address. It's recorded with each file, but files are always served to people through the API.keyPrefixdefaults touploads.- Use a dedicated, private bucket. The server checks that the bucket isn't public before it starts listening, and refuses to start if it can't confirm that:
AWS S3: turn on all four Block Public Access settings. The server also needs
s3:GetBucketPublicAccessBlockto check them:shaws s3api put-public-access-block --bucket my-refract-media \ --public-access-block-configuration BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=trueMinIO: remove anonymous access with
mc anonymous set none ALIAS/my-refract-media.Other providers must let the server read the bucket policy, bucket and object ACLs, and list objects (
s3:ListBucketVersions,s3:GetObjectVersionAcl). On these it scans every object's ACL at startup, so a large bucket takes longer to start.
Besides those checks, the server needs ordinary GetObject, PutObject and DeleteObject access.
What people can upload ​
The file size limit is set in the app, under Manage instance → Config → Instance features: 100 MB per file by default, up to 1,024 MB. Uploads are held in memory before they're stored, so a higher limit needs more memory per concurrent upload. Your reverse proxy must allow the same size.
The accepted file types are set in app.config.yaml:
yaml
media:
uploads:
# message attachments
attachmentExtensions: [png, jpg, jpeg, gif, webp, pdf, txt, md, json, csv, zip, mp4, webm, mp3, wav, ogg]
# avatars, icons and banners
imageExtensions: [png, jpg, jpeg, gif, webp]
maxAttachmentsPerMessage: 10Leave the block out to use those defaults. Each setting can also come from the environment, which wins: MEDIA_ATTACHMENT_EXTENSIONS="png,jpg,pdf", MEDIA_IMAGE_EXTENSIONS="png,jpg,webp" and MEDIA_MAX_ATTACHMENTS_PER_MESSAGE=10.
Video avatars and banners (WebM) are re-encoded with ffmpeg, which the Docker image includes.
Cleaning up ​
Removed and abandoned uploads are cleaned up with the --prune-media command; see Administration.