Accepted
Pluralscape stores user-uploaded media: avatars, multi-photo galleries, group images, littles safe mode content (links, videos, images), and import archives (SP avatar ZIPs). The storage layer must support:
- E2E encryption — media encrypted client-side before upload, server stores opaque blobs
- Thumbnailing — preview images for galleries and avatars without downloading full-size originals
- Self-hosting without mandatory cloud services
- Quotas and retention policies
- Efficient image delivery on mobile (low-spec devices, metered connections)
- Image editing — crop and resize before upload (no external tool required)
PostgreSQL/SQLite are unsuitable for blob storage at scale (bloated databases, backup complexity, no streaming).
Evaluated: S3-compatible object storage, PostgreSQL large objects, SQLite BLOB columns, filesystem-only.
S3-compatible object storage for all media, with MinIO as the self-hosted option and local filesystem as a minimal fallback.
- User selects/captures an image → client offers crop/resize UI
- Client generates a thumbnail (separate blob)
- Client encrypts both full-size and thumbnail with the appropriate key (tier 1 for private, bucket key for shared)
- Client uploads encrypted blobs to S3-compatible storage via presigned URLs
- Server stores a metadata record (object key, size, content type, encryption metadata) — never the plaintext
- On retrieval: client downloads encrypted blob → decrypts → displays
| Tier | Backend | Notes |
|---|---|---|
| Hosted | Any S3 provider (AWS S3, Cloudflare R2, Backblaze B2) | Standard presigned URL flow |
| Self-hosted (full) | MinIO container in Docker Compose | S3-compatible API, single container |
| Self-hosted (minimal) | Local filesystem | Bun serves files directly, no S3 dependency |
- Client-side encryption only — the server and storage backend never see plaintext media. This means no server-side image processing, transcoding, or CDN caching of readable images.
- Thumbnails are separate encrypted blobs — generated client-side at upload time. Avoids downloading multi-MB images for list views.
- Presigned URLs for upload/download — the API server generates time-limited S3 URLs. The client uploads directly to storage, reducing API server bandwidth.
- Local filesystem fallback — for minimal self-hosted deployments, blobs are stored in a configurable directory. The API server handles uploads/downloads directly (no presigned URLs).
- S3-compatible API — same client code works against any S3 provider or MinIO
- Single binary / single container deployment
- AGPL-3.0 licensed — same license as Pluralscape
- Battle-tested, Linux Foundation CNCF project
- Supports bucket policies, lifecycle rules, and quotas
- No server-side image processing. All crop/resize/thumbnail work happens on the client, which increases client complexity and may be slow on low-spec devices
- Encrypted blobs cannot be CDN-cached in readable form. Cache only the encrypted bytes; decryption happens on-device
- Local filesystem fallback lacks the durability guarantees of S3. Self-hosters using the minimal tier should configure their own backup strategy
- Three storage backends increase testing surface (S3, MinIO, local filesystem)
- Import of SP avatar ZIPs requires client-side extraction, encryption, and upload of potentially hundreds of images. The flow must be chunked with progress indication
MinIO: AGPL-3.0 (compatible, same license). AWS SDK: Apache 2.0 (compatible).