workboardworkboard

Storage backends

Overview

Workboard uses S3-compatible object storage for uploads in:

  • task descriptions
  • task comments

The browser uploads directly to the configured storage backend using presigned URLs.

Workboard then serves uploaded assets back through its own API.

Current behavior:

  • images render inline
  • non-image files such as CSV, PDF, and ZIP render as attachment cards/links
  • assets are private by default

That means one rule matters for every backend:

  • S3_ENDPOINT must be reachable by the browser

Do not use a Docker-internal hostname such as http://minio:9000 for a public deployment unless the browser can actually reach it.

Required Workboard variables

S3_ENDPOINT=
S3_BUCKET=
S3_ACCESS_KEY_ID=
S3_SECRET_ACCESS_KEY=
S3_REGION=us-east-1
S3_FORCE_PATH_STYLE=false

Notes:

  • S3_FORCE_PATH_STYLE=true is usually needed for MinIO.
  • S3_FORCE_PATH_STYLE=false is usually correct for AWS S3 and R2.
  • S3_PUBLIC_BASE_URL is optional and not required for the current private asset flow.

MinIO

MinIO is the recommended self-hosted option.

Local Docker setup

For local development, this is fine:

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

This works when Workboard and MinIO are on the same Docker network and your browser reaches MinIO through localhost.

Public deployment

For a public deployment, expose MinIO on its own hostname through your reverse proxy.

Example:

  • Workboard: https://work.ravefox.dev
  • MinIO: https://files.cloud.work.ravefox.dev

This can be done with Caddy, Nginx, Traefik, or any other reverse proxy.

Then use:

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

You also need:

  • a created bucket

  • a dedicated, non-root MinIO user with access limited to that bucket

  • MinIO CORS allowing your Kaneo origin

  • no anonymous bucket read policy is required

    Never expose MinIO with its default minioadmin root credentials. Generate unique credentials for every deployment and keep the MinIO Console private.

AWS S3

AWS S3 is the simplest managed option.

Use a bucket and an IAM user with access to that bucket.

Example:

S3_ENDPOINT=https://s3.us-east-1.amazonaws.com
S3_BUCKET=kaneo-uploads
S3_ACCESS_KEY_ID=AKIA...
S3_SECRET_ACCESS_KEY=...
S3_REGION=us-east-1
S3_FORCE_PATH_STYLE=false

For another AWS region, adjust:

  • S3_ENDPOINT

Using an IAM role instead of an access key

If Kaneo runs on AWS (EC2, ECS, or EKS), you can omit S3_ACCESS_KEY_ID and S3_SECRET_ACCESS_KEY entirely and let Kaneo authenticate with the instance's IAM role. Leave both unset and the AWS SDK resolves credentials from its default provider chain (instance profile, ECS task role, or EKS IRSA).

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 omitted; the IAM role is used

Set both keys or neither: configuring only one is rejected. The role must allow s3:PutObject, s3:GetObject, and s3:DeleteObject on the bucket.

Recommended S3 CORS policy:

[
  {
    "AllowedHeaders": ["*"],
    "AllowedMethods": ["GET", "PUT", "HEAD"],
    "AllowedOrigins": ["https://work.ravefox.dev"],
    "ExposeHeaders": ["ETag"]
  }
]

Cloudflare R2

R2 works well because it exposes an S3-compatible API.

Use your account endpoint, bucket, and R2 access keys.

Example:

S3_ENDPOINT=https://<account-id>.r2.cloudflarestorage.com
S3_BUCKET=kaneo-uploads
S3_ACCESS_KEY_ID=...
S3_SECRET_ACCESS_KEY=...
S3_REGION=auto
S3_FORCE_PATH_STYLE=false

Notes:

  • S3_REGION=auto is typical for R2
  • a public bucket is not required for Workboard's current private asset flow

One copy-paste self-hosted example

This example gives you Workboard + MinIO in one Compose file.

services:
  postgres:
    image: postgres:16-alpine
    env_file:
      - .env
    volumes:
      - postgres_data:/var/lib/postgresql/data
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U kaneo -d kaneo"]
      interval: 10s
      timeout: 5s
      retries: 5

  api:
    image: grootbro/workboard:latest
    env_file:
      - .env
    depends_on:
      postgres:
        condition: service_healthy
    restart: unless-stopped

  web:
    image: grootbro/workboard:latest
    env_file:
      - .env
    depends_on:
      - api
    restart: unless-stopped

  minio:
    image: minio/minio:latest
    command: server /data --console-address ":9001"
    env_file:
      - .env
    volumes:
      - minio_data:/data
    restart: unless-stopped

volumes:
  postgres_data:
  minio_data:

Matching .env:

KANEO_CLIENT_URL=https://work.ravefox.dev
KANEO_API_URL=https://work.ravefox.dev/api

MINIO_ROOT_USER=<generate-a-unique-admin-user>
MINIO_ROOT_PASSWORD=<generate-a-strong-random-secret>

S3_ENDPOINT=https://files.cloud.work.ravefox.dev
S3_BUCKET=kaneo-uploads
S3_ACCESS_KEY_ID=<generate-a-unique-admin-user>
S3_SECRET_ACCESS_KEY=<generate-a-strong-random-secret>
S3_REGION=us-east-1
S3_FORCE_PATH_STYLE=true

Before testing uploads:

  1. Create the kaneo-uploads bucket.
  2. Configure MinIO CORS for your Workboard origin.
  3. Make sure files.cloud.work.ravefox.dev resolves publicly.

Troubleshooting

If uploads fail:

  • check that the bucket exists
  • check that S3_ENDPOINT is public and browser-reachable
  • check CORS on your storage backend
  • check that the access key can PutObject and GetObject