Skip to content

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: /data

Files 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_ID and AWS_SECRET_ACCESS_KEY (and AWS_SESSION_TOKEN for temporary ones), or the host's IAM role. The server refuses to start if the config file contains keys.
  • publicBaseUrl is required: the bucket's own address. It's recorded with each file, but files are always served to people through the API.
  • keyPrefix defaults to uploads.
  • 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:GetBucketPublicAccessBlock to check them:

      sh
      aws s3api put-public-access-block --bucket my-refract-media \
        --public-access-block-configuration BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true
    • MinIO: 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: 10

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

Released under the GNU Affero General Public License v3.0.