Files that outlive the container
Production File Storage
Caspian carries uploads through route-owned RPC actions. In production, keep user file bytes in object storage, keep durable metadata in your database, and return short-lived access URLs to the browser.
Storage is an application integration
There is no storage flag in
caspian.config.json. Caspian owns the upload
transport, validation boundary, auth policy, and public-file safety rules;
your application chooses the durable object-storage provider and keeps its
adapter in src/lib/storage/** when multiple routes use it.
Choose storage by environment
| Environment | File bytes | Metadata | Why |
|---|---|---|---|
| Local development | public/uploads/** | Database when needed | Simple, inspectable, and served safely by the local app. |
| Single durable server | Persistent mounted volume or object storage | Database | A mounted volume can work, but backups and migration remain your responsibility. |
| Containers or replicas | Object storage | Database | Container disks are ephemeral and separate replicas do not share one filesystem. |
| Static export | Pre-existing public assets only | Build-time data | RPC uploads are inert without the Python server. |
The production upload path
Route-owned RPC
Accept the named file through @rpc() and protect the action with route auth policy.
Trust the server check
Enforce size, content type, extension, ownership, and a generated storage key before writing bytes.
Move blocking SDK work off-loop
If the provider SDK is synchronous, call it through asyncio.to_thread(...).
Keep identifiers, not secrets
Store the provider file id, object key, size, MIME type, owner, and timestamps in Prisma.
Resolve access URLs
Return a public CDN URL or a short-lived signed URL; never expose provider credentials.
Complete the lifecycle
Delete object variants and metadata together, with explicit retry or cleanup behavior for partial failure.
Recommended managed storage
Dilnaka Storage
Dilnaka provides S3-backed file storage through a typed Python SDK. The application receives a short-lived presigned upload or access URL, while bucket credentials stay outside the Caspian process.
# pyproject.toml dilnaka>=0.0.9 # .env — keep server-side DILNAKA_API_KEY=dlk_dev_your_api_key_here
No AWS keys in the app
The SDK works through scoped, short-lived URLs instead of distributing storage credentials.
Resumable large uploads
Multipart transfers keep memory flat and retry a failed part instead of restarting the whole file.
Expiring access links
Resolve temporary access when needed and refresh links before they expire during an active session.
Shared adapter pattern
The reference Caspian application keeps provider calls in one
src/lib/storage/dilnaka_storage.py adapter.
It validates image types and sizes, creates display and thumbnail variants,
offloads the blocking SDK, refreshes signed URLs near expiry, and deletes every
stored variant when a record is replaced or removed.
async def get_access_url(file_id: str, expires_in: int): client = _get_client() result = await asyncio.to_thread( client.get_file_access_url, file_id, expires_in, ) return result.url
What the database keeps
- Provider file id and object key
- Owner or parent-record relationship
- Original name, content type, and byte size
- Variant ids for thumbnails or optimized media
- Created, updated, and deletion state
Signed URLs are delivery credentials, not durable identity. Always keep the provider id so the application can re-sign or delete the object later.
Production checklist
Credentials remain server-side and are injected through the deployment environment.
Uploads require auth when the stored object belongs to a user or tenant.
Validation runs before provider upload, including an explicit size ceiling.
Database and object deletion failures are observable and retryable.
Access URLs use the shortest lifetime that still supports the user flow.
Backups cover metadata; lifecycle and retention rules cover object bytes.
File Uploads
Build the route-owned RPC and PulsePoint progress flow.
Deployment
Configure persistent services and production environment values.