Object storage and uploads
Overview
Workboard supports private uploads in task descriptions and task comments.
Uploads use an S3-compatible object storage backend:
- the Workboard API creates presigned upload URLs
- the browser uploads files directly to the storage backend
- Workboard finalizes uploads into private asset records
- Workboard serves uploaded files back through its own API
This means Workboard can work with multiple backends as long as they expose a compatible S3-style API.
Current behavior:
- images render inline
- other files such as CSV, PDF, or ZIP are inserted as attachment cards/links
- uploaded files are private by default and are not meant to be served from public bucket URLs
If you want backend-specific setup examples, see the storage backends guide.
Recommended local setup: MinIO
For local or self-hosted deployments, MinIO is the recommended storage backend.
It gives you:
- a stable S3-compatible API
- a web console for bucket management
- a simple Docker setup
- better compatibility for direct browser uploads
Example .env values:
MINIO_ROOT_USER=kaneo-local
MINIO_ROOT_PASSWORD=<choose-a-local-development-password>
S3_ENDPOINT=http://minio:9000
S3_BUCKET=kaneo-uploads
S3_ACCESS_KEY_ID=kaneo-local
S3_SECRET_ACCESS_KEY=<choose-a-local-development-password>
S3_REGION=us-east-1
S3_FORCE_PATH_STYLE=true
Create the bucket before using uploads.
You do not need to make the bucket public.
Important: internal Docker URLs are not enough in production
When Workboard generates a presigned upload URL, the browser uploads directly to your storage backend.
That means S3_ENDPOINT must be reachable by the browser, not just by Docker containers.
For example:
http://minio:9000works only inside Dockerhttps://files.example.comworks from the browser
If your Workboard deployment is public, do not leave S3_ENDPOINT set to http://minio:9000.
Use a public MinIO hostname instead, for example:
S3_ENDPOINT=https://files.cloud.work.ravefox.dev
S3_BUCKET=kaneo-uploads
S3_ACCESS_KEY_ID=kaneo
S3_SECRET_ACCESS_KEY=<generate-a-strong-random-secret>
S3_REGION=us-east-1
S3_FORCE_PATH_STYLE=true
Never expose MinIO with its default minioadmin root credentials. Create a
dedicated, bucket-scoped user and generate unique credentials for every
deployment.
Recommended public setup
The simplest production setup is:
cloud.work.ravefox.devfor Workboardfiles.cloud.work.ravefox.devfor MinIO
Expose MinIO on its own public hostname through your reverse proxy.
Why this is needed:
- Workboard signs uploads against
S3_ENDPOINT - the browser uses that signed URL directly
- Docker-internal names like
minioare not resolvable from a user's browser
Using a dedicated subdomain is recommended over proxying MinIO under a path.
Reads do not need a public bucket URL, because Workboard serves uploaded assets back through /api/asset/:id.
Other S3-compatible backends
Workboard is not tied to MinIO. Any deployment can point Workboard at another S3-compatible backend by changing the S3_* environment variables.
Examples:
- AWS S3
- Cloudflare R2
- MinIO
fs
Using fs
fs is another S3-compatible option if you want a lightweight local object store. It is built by friends of Workboard.
When using fs, configure Workboard like this:
S3_ENDPOINT=http://fs:2600
S3_BUCKET=kaneo-uploads
S3_ACCESS_KEY_ID=<your-access-key>
S3_SECRET_ACCESS_KEY=<your-secret-key>
S3_REGION=us-east-1
S3_FORCE_PATH_STYLE=true
Important notes for fs:
- buckets are created through the S3 API or CLI, not through a web dashboard
- Workboard still expects the bucket to exist before uploads start
- direct browser uploads may need reverse-proxy CORS headers, because
fsdoes not currently implement S3 CORS APIs
Required Workboard storage variables
Set these in your Workboard deployment:
| Name | Description |
|---|---|
S3_ENDPOINT |
S3-compatible API endpoint used by Workboard |
S3_BUCKET |
Bucket used for uploaded files |
S3_ACCESS_KEY_ID |
Access key used to create presigned upload URLs. Optional when using an IAM role (see below) |
S3_SECRET_ACCESS_KEY |
Secret key used to create presigned upload URLs. Optional when using an IAM role (see below) |
S3_REGION |
Region used for signing |
S3_FORCE_PATH_STYLE |
Usually true for MinIO and fs |
Optional:
| Name | Description |
|---|---|
S3_PUBLIC_BASE_URL |
Optional public asset base URL. Workboard does not require this for the current private asset flow. |
S3_MAX_IMAGE_UPLOAD_BYTES |
Maximum allowed upload size in bytes for images and other uploaded files |
S3_PRESIGN_TTL_SECONDS |
Presigned upload URL lifetime |
Using an IAM role (AWS S3) instead of static keys
When Kaneo runs on AWS (EC2, ECS, or EKS), you can let it authenticate with an IAM role instead of long-lived access keys.
Leave both S3_ACCESS_KEY_ID and S3_SECRET_ACCESS_KEY unset. Kaneo then falls
back to the AWS SDK's default credential provider chain, which automatically
picks up credentials from:
- an EC2 instance profile
- an ECS task role
- an EKS service account / IRSA (
AWS_WEB_IDENTITY_TOKEN_FILE) - standard
AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEYenvironment variables - a shared
~/.aws/credentialsprofile
Example for AWS S3 with an attached IAM role:
S3_ENDPOINT=https://s3.us-east-1.amazonaws.com
S3_BUCKET=kaneo-uploads
S3_REGION=us-east-1
S3_FORCE_PATH_STYLE=false
# S3_ACCESS_KEY_ID and S3_SECRET_ACCESS_KEY intentionally omitted
The IAM role must allow s3:PutObject, s3:GetObject, and s3:DeleteObject
on the bucket.
Set both keys or neither. Configuring only one of S3_ACCESS_KEY_ID /
S3_SECRET_ACCESS_KEY is treated as a misconfiguration and Kaneo will refuse
to start the storage client.
Testing uploads
Once storage is configured:
- Open a task.
- Paste, drag, or select a file inside the description or a comment.
- Confirm images render inline and other files render as attachment cards/links.
If uploads fail:
- confirm the bucket exists
- confirm the credentials can write to the bucket
- confirm the storage endpoint is reachable from the browser
- confirm
S3_ENDPOINTis a public URL, not an internal Docker hostname - if using direct browser uploads, confirm CORS is configured correctly for your storage endpoint