Prompt
I'm building an app with uploads, thumbnails, and exports. What file storage setup makes sense?
Latest observation
For an app with uploads, thumbnails, and exports, the usual best-practice setup is:
Recommended storage architecture
1) Use object storage for all user files
Store originals, thumbnails, and exports in something like:
- Amazon S3
- Google Cloud Storage
- Azure Blob Storage
- or an S3-compatible service like Cloudflare R2, Backblaze B2, MinIO for self-hosting
This gives you:
- scalable storage
- cheap durability
- easy CDN integration
- simple lifecycle rules
- good fit for generated assets like thumbnails and exports
2) Keep the database for metadata only
Store only pointers and metadata in your DB, not the file contents.
Typical DB fields:
- file ID
- user ID / tenant ID
- original filename
- MIME type
- size
- storage key/path
- checksum
- upload status
- derived asset status
- timestamps
This makes querying and permissions easy without bloating your DB.
3) Separate originals from derived files
Use distinct logical prefixes/buckets for:
- original uploads
- thumbnails
- exports
- optionally temporary processing files
Example structure:
uploads/{userId}/{fileId}/originaluploads/{userId}/{fileId}/thumb-256.webpexports/{userId}/{jobId}/report.pdf
This helps with:
- lifecycle management
- cleanup
- permissions
- CDN behavior
- observability
A solid default setup
Buckets
If you want a simple but clean setup:
app-uploads: original filesapp-derived: thumbnails, previews, exports
Or, if you prefer stronger separation:
app-private: originals and exportsapp-public: public thumbnails/previews
For most apps, two buckets is a good balance.
Access pattern
Uploads
- Client uploads directly to object storage using a pre-signed URL
- Backend issues the signed URL after auth/validation
- Backend stores metadata in DB
This avoids routing large files through your app server.
Thumbnails
- Generate asynchronously after upload
- Store thumbnail back in object storage
- Serve via CDN if public, or via signed URLs if private
Exports
- Generate in a background job
- Store result in object storage
- Notify user when ready
- Provide download link or signed URL
Public vs private
Make thumbnails public only if safe
If thumbnails do not expose sensitive data, they can be:
- public behind a CDN, or
- private with short-lived signed URLs
Keep originals and exports private by default
This is usually safest, especially if files are user-uploaded or contain sensitive content.
Lifecycle rules you’ll want
Set automated cleanup policies:
- delete failed temp uploads after hours/days
- expire old exports after a retention period
- optionally delete unused thumbnails if source file is removed
- version or archive old originals if needed
CDN recommendation
Put a CDN in front of:
- thumbnails
- public previews
- maybe exports if they’re frequently downloaded
Don’t usually put private originals directly public.
If you expect larger scale
Consider this split:
- Object storage for files
- Postgres/MySQL for metadata
- Redis/queue for background thumbnail/export jobs
- CDN for delivery
- presigned uploads/downloads
A simple “good default” design
If you want one concrete recommendation:
- S3-compatible object storage
- Postgres for metadata
- Private bucket for originals and exports
- Public or CDN-served bucket/prefix for thumbnails
- Presigned URLs for upload/download
- Background worker for thumbnails and exports
- Lifecycle rules for temp/expired files
If you want, I can sketch a bucket/key naming scheme, or suggest a setup optimized for small startup, enterprise, or serverless architecture.