Skip to content

Object storage

Bucket Owner Contents
Platform bucket (AWS_BUCKET, e.g. ulams) Platform Platform files, h5p/ for the platform’s H5P content
ulams-<slug> (TENANCY_BUCKET) One tenant Course files, images, videos and HLS renditions, LiaScript assets, H5P content and temporary files under h5p/, and SCORM/cmi5 packages when their disk is s3

ulams:tenant:create creates the tenant bucket and attaches a public-read bucket policy (S3BucketProvisioner): files are served to browsers by URL. The bucket’s public base URL is TENANCY_STORAGE_PUBLIC_URL plus /<bucket> and is written to the tenant’s AWS_URL.

All tenants use the platform’s access key: isolation is per bucket, not per credential (a known limit recorded in api/docs/multidomain.md).

Where Variables
api (platform) FILESYSTEM_DRIVER=s3, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_DEFAULT_REGION, AWS_BUCKET, AWS_ENDPOINT, AWS_URL, AWS_USE_PATH_STYLE_ENDPOINT
api (bucket creation) TENANCY_S3_ENDPOINT, TENANCY_S3_KEY, TENANCY_S3_SECRET, TENANCY_S3_REGION (default to the AWS_* values), TENANCY_BUCKET, TENANCY_STORAGE_PUBLIC_URL
api (package disks) SCORM_DISK (default local), CMI5_DISK (default: the value of SCORM_DISK), LIASCRIPT_DISK (default FILESYSTEM_DRIVER)
h5p S3_ENDPOINT, S3_REGION, S3_KEY, S3_SECRET, S3_BUCKET, S3_PREFIX (h5p), S3_FORCE_PATH_STYLE; per tenant, the AWS_* values of its env file win

AWS_ENDPOINT is the address the containers use; AWS_URL and TENANCY_STORAGE_PUBLIC_URL are what browsers use. They differ when the store sits on the internal network (the example’s MinIO: http://minio:9000 inside, https://storage.<content domain> outside).

  • A managed S3-compatible service (AWS S3, Cloudflare R2, Backblaze B2, Scaleway, OVH and others). The access key needs CreateBucket and PutBucketPolicy for tenant provisioning. On AWS, S3 Block Public Access rejects public bucket policies by default; tenant provisioning fails at the bucket step until you allow public policies for these buckets (or pre-create them and serve files through a CDN). Not tested by the project.
  • Self-hosted. The development stack uses MinIO, and the example offers it behind --profile minio. Recommended evaluate SeaweedFS or RustFS instead: the project plans to move to a permissively licensed default.

Uploaded files are untrusted. The API stores SVG, HTML and XML outside package paths with Content-Disposition: attachment and sets Content-Type from the extension (ActiveContentSafeS3Adapter), and the storage host in the Caddyfile adds nosniff and a script-src 'none'; sandbox policy for active file types. Put the public storage host on the content domain, not on the application domain. See Security.