Skip to content

Files and S3

By default uploads land in DATA_DIR/files, sharded across subdirectories, and are served by the app itself with the same authentication as everything else, including Range requests and ETags. Images get a thumbnail generated at upload time.

Nothing is public. A file URL without a session returns 401.

Terminal window
STORAGE=s3
S3_BUCKET=slick-files
S3_ACCESS_KEY=...
S3_SECRET_KEY=...
S3_REGION=us-east-1
S3_FORCE_PATH_STYLE=false # true for MinIO
S3_ENDPOINT=https://... # omit for AWS

Uploads and thumbnails then live in the bucket, but are still served through the app with the same authentication, Range, and ETag behaviour. There are no presigned URLs and nothing in the bucket is public, which means the bucket does not need to be either.

The trade-off is that file bytes pass through your server rather than going straight from the bucket to the browser. For the team sizes Slick targets that is the right trade: it keeps one permission model instead of two.

An upload to an encrypted channel arrives already encrypted, and is stored as an opaque blob with a generic name and type. No thumbnail is generated, because there is nothing the server can decode. This applies equally to local disk and S3: the bucket holds ciphertext.

An upload that never gets attached to a message is deleted after 24 hours by a background job. Avatars and workspace icons are message-less by design and are excluded from that sweep.

The default cap is 50 MB per file. Uploads stream to storage rather than buffering in memory, so the limit is about what you want to store, not what the process can hold.