Moving my Django Portfolio from SQLite to PostgreSQL was straightforward. Moving my media files to the cloud was not — at least not until I understood the real problem.
When you deploy a Django app to a platform like Render, the filesystem is ephemeral. Any file written to disk during runtime — like an uploaded image — disappears the next time your service restarts or redeploys. My project images were stored in core/media/, served by Django's FileSystemStorage. That works fine in development. In production it means every deploy wipes the images.
My first instinct was to commit core/media/ to Git and push it to GitHub. That would at least survive deploys. But it is the wrong solution for several reasons:
The real solution is cloud object storage: a dedicated service built for storing and serving files.
Cloudflare R2 — 10 GB free storage and zero egress fees, which is unusually generous. It uses an S3-compatible API, so it works with django-storages. The limitation: it is storage only. Cloudflare does have a database product called D1, but it is SQLite-based and designed for edge workers — not suitable for a standard Django application.
Cloudinary — The easiest Django integration, with a dedicated package that makes setup almost plug-and-play. It also handles image transforms (resize, crop) for free. 25 GB storage and 25 GB bandwidth per month on the free tier. The trade-off: one more provider to manage alongside the database.
Backblaze B2 — The cheapest option beyond the free tier ($0.006/GB). 10 GB free storage. S3-compatible and works with django-storages. A strong choice if cost is the main concern.
Supabase — PostgreSQL database plus built-in file storage in one place, on the same free plan (500 MB database, 1 GB storage). The storage API is S3-compatible, so django-storages and boto3 work without any vendor-specific library.
I was already planning to use Supabase for my PostgreSQL database. Storage being included in the same free plan, on the same dashboard, with the same project credentials, made the decision simple. One provider, one account, one place to manage everything.
For a portfolio project with around 20 images and low traffic, the free tier is more than enough. The S3-compatible API meant the integration on the Django side would follow the same pattern as any other S3-compatible service — no surprises.
What I did not anticipate was how many small details the integration would involve. That is the subject of the next article.