Prompt
How do I store user uploads for a SaaS app across multiple instances?
Latest observation
Oct 12, 2026
OpenAI APIWeb search: off
For a SaaS app running on multiple instances, the key rule is:
don’t store uploads on local disk if any instance might need to serve or process them later.
Use shared, durable storage instead.
Recommended approach
1) Store uploads in object storage
Use something like:
- Amazon S3
- Google Cloud Storage
- Azure Blob Storage
- or an S3-compatible service like MinIO
Your app instances upload files to object storage, and later retrieve them from there.
2) Keep only metadata in your database
Store things like:
- file ID
- user ID / tenant ID
- original filename
- storage key/path
- MIME type
- size
- checksum
- upload status
- created_at
Example:
{
"id": "file_123",
"tenant_id": "tenant_9",
"user_id": "user_42",
"original_name": "invoice.pdf",
"storage_key": "tenants/tenant_9/uploads/file_123.pdf",
"content_type": "application/pdf",
"size_bytes": 248122,
"checksum": "sha256:...",
"created_at": "2026-10-12T10:00:00Z"
}
Common upload patterns
A) App server receives upload, then writes to object storage
Simple to implement, but your server handles file traffic.
Flow:
- User uploads to your API
- API validates auth/tenant limits
- API streams file to S3/GCS/etc.
- API stores metadata in DB
Good for small/medium traffic.
B) Direct-to-object-storage uploads using signed URLs
Best for scale.
Flow:
- Client asks your backend for an upload URL
- Backend authenticates user and returns a pre-signed URL
- Client uploads directly to storage
- Client notifies backend (or backend listens to storage events)
- Backend records metadata
This avoids routing large files through your app instances.
Why not local disk?
If you have multiple instances:
- one instance may receive the upload
- another instance may later try to read it
- autoscaling or restarts can delete local files
- load balancers make request routing non-deterministic
So local disk is only okay for:
- temporary processing
- cache
- single-instance dev/test setups
Multi-tenant considerations
For SaaS, isolate by tenant:
- prefix objects by tenant ID
- enforce access checks in your backend
- optionally use separate buckets for high-isolation customers
Example key structure:
tenants/{tenant_id}/users/{user_id}/uploads/{file_id}
Security best practices
- Validate file type and size
- Use signed URLs with short expirations
- Store files privately by default
- Scan uploads for malware if needed
- Consider encryption at rest
- Use least-privilege IAM permissions
- Don’t trust client-provided filenames or content types
Serving files
Options:
- backend generates signed download URLs
- CDN in front of object storage for public-ish assets
- proxy through your API if you need strict authorization
If you need processing
For thumbnails, OCR, conversions, etc.:
- Upload to object storage
- Enqueue a job
- Worker reads from object storage
- Worker writes processed result back to object storage
- Update DB status
Practical default architecture
- Frontend → requests upload URL from backend
- Backend → authenticates, returns signed URL
- Client → uploads directly to S3/GCS
- DB → stores file metadata
- Workers → process files asynchronously
- Backend → returns signed download URLs when needed
If you want, I can sketch:
- a simple architecture diagram,
- a database schema for uploads, or
- an implementation example in your stack (Node, Python, Go, etc.).