File storage: local or S3
Attachments, inline images, profile photos and data exports are stored by one of two drivers. File metadata is in the database; the files are on disk or in a bucket. Both are needed for a complete backup.
Local volume (default)
STORAGE_DRIVER=local
STORAGE_LOCAL_DIR=/data/uploads
The example compose.yml mounts the uploads volume at /data/uploads. Files are only ever served through the app,
after an access check. Nothing else to configure.
S3-compatible object storage
Any S3 API works: AWS S3, Cloudflare R2, Hetzner Object Storage, Garage, SeaweedFS and others. The store must support multipart uploads and pre-signed download URLs.
To use it:
- Create a bucket (private; no public access needed).
- Create credentials that may read, write and delete objects in that bucket, including multipart uploads.
- Set the variables and restart with
docker compose up -d:
STORAGE_DRIVER=s3
S3_ENDPOINT=https://s3.eu-central-1.amazonaws.com
S3_REGION=eu-central-1
S3_BUCKET=sol2flow-files
S3_ACCESS_KEY_ID=…
S3_SECRET_ACCESS_KEY=…
S3_FORCE_PATH_STYLE=false
| Provider | S3_ENDPOINT | S3_REGION | S3_FORCE_PATH_STYLE |
|---|---|---|---|
| AWS S3 | empty, or https://s3.<region>.amazonaws.com | the bucket's region | false |
| Cloudflare R2 | https://<account id>.r2.cloudflarestorage.com | auto | true |
| Self-hosted (Garage, SeaweedFS, …) | the store's S3 API address | as configured | true |
Downloads go straight to the bucket
When someone downloads a file, the app checks access and then redirects the browser to a short-lived signed link on the
bucket. So the bucket's API address must be reachable from your users' browsers. If the app reaches the store under
an internal name (for example http://garage:3900), set S3_PUBLIC_ENDPOINT to the public address browsers use. No CORS
rule is needed.
Upload chunks and S3
Uploads are sent in chunks (Admin → General → Uploads → Chunk size, default 8 MB). S3 requires parts of at least 5 MB (except the last), so on S3 the chunk size can't be set lower than 5 MB. A file can have at most 10,000 parts.
Clean up unfinished uploads
sol2flow cancels abandoned uploads every day. As a second safety net, add a lifecycle rule to the bucket that aborts incomplete multipart uploads after 2–3 days.
Switching drivers
You can switch from local to S3 (or back) at any time. New files go to the new storage; existing files are read from
where they were stored, as long as both stay configured and reachable (keep the volume mounted and the S3_*
variables set). Existing files are not moved to the new storage.
Upload limits
The maximum file size and the chunk size are set in Admin → General → Uploads (see Instance settings). Remember the proxy body limit when you raise the chunk size.