Keep user uploads safe with persistent storage (/bw-data)

Why uploaded pictures vanish after a new release, and three ways to turn on persistent storage at /bw-data on BrewInTH, with the size rules and a test.
You asked an AI to build a site where members upload a profile picture or customers attach a payment slip. It worked in testing. Then you changed a line, released a new version, and every uploaded picture was gone — broken image frames everywhere. Or worse, the app could not save a single upload and showed Read-only file system.
That is not the AI's bug and the site is not broken. It is how apps run on BrewInTH. This article explains why the files disappear and how to turn on Persistent storage (/bw-data) so pictures, documents and anything your users send survive every new release.
In short
📁 Every release starts the app from the new code, so files the app wrote inside its code folder do not carry over
📁 Database data never disappears; files (images, PDFs, SQLite files) belong in persistent storage
📁 Three ways to turn it on: yourself under App settings, through your AI agent, or automatically on Upload & go live
📁 All projects together may use up to your package's storage; a volume can grow but never shrink
📁 The Backups add-on backs up /bw-data too
Why uploaded files disappear
Think of your app as a shop that is refitted from scratch with every new version. BrewInTH builds the app fresh from the latest code each time. The upside: every release is clean, matches the code you sent, and carries no leftovers from the last one.
The catch: files the app writes into its code folder do not survive a release. On BrewInTH the app's folder is also read-only, so an app that tries to write there fails at once with Read-only file system, instead of seeming to work and losing the files quietly later.
Two things do survive:
- Data in a database (PostgreSQL, MySQL, MariaDB, MongoDB) lives apart from the app. Release as often as you like; the data stays.
- Files in persistent storage, a disk that belongs to the project and is attached to every version of the app at
/bw-data.

Static sites (HTML, CSS and JavaScript with no server program) never need this and do not show the panel, because they write no files of their own.
What goes where
A simple rule:
- Rows of data — members, orders, survey answers — go in the database.
- Whole files — profile pictures, payment slips, PDF receipts, attachments — go under
/bw-data, with only the file name or path stored in the database. - Apps on SQLite (the whole database is one file such as
app.db) must keep that file under/bw-data, or the data resets with every release.
Three ways to turn on persistent storage

1. Yourself, in the console
- Open the project and go to the App settings tab.
- Scroll to the Persistent storage (/bw-data) panel.
- Pick a Size. The list goes up to 80% of the package space still free.
- Press Save. It applies on the next release or restart.
Then tell your AI to write files under /bw-data only, for example:
This app lets users upload a profile picture and a payment slip.
Store every uploaded file under /bw-data/uploads only
(create the subfolders if they are missing) and keep just the file name in the database.
Add a route that serves a picture from that folder, checking the visitor may see that file.
Accept images and PDFs only, and limit each file to 5 MB.
/data, the earlier name, is the very same folder. An app that already writes to /data keeps working, but new code should use /bw-data.
2. Through your AI agent
If a coding agent (Claude Code, Codex, Cursor, Windsurf) deploys your site with the link and token, it can set this up itself with the storage_set tool — and do more than attach a disk. It can name the app's own folders, such as uploads or data, and those folders are then served from persistent storage with no change to the code. An app that already writes to uploads/ simply keeps doing so.
Before a release, the platform also reads the code in the ZIP. If the code writes files to a folder that is not on persistent storage, it warns the AI and names the call to make, so most agents fix it in one go. To be explicit, add this to the chat:
Before deploying, find every folder the app writes files to (user uploads, SQLite files, caches)
and use storage_set to put those folders on persistent storage.
After the release, upload one test file, release once more, and confirm the file still opens.
3. Upload & go live does it for you
If you send a ZIP yourself through Upload & go live (still in BETA), the platform reads the code, sees which folders the app writes to — uploads/, data/, a SQLite file — and turns persistent storage on by itself. Files already in that folder inside the ZIP (existing pictures, or a SQLite file that holds data) are copied onto the volume the first time.
When you send a newer ZIP later, the data on the site is not overwritten. If you really mean to replace it with the data files of the new ZIP, tick Use the data files of this archive instead of the site's before sending.
Rules to know before you pick a size

- It uses your package's storage. The persistent storage of all projects in the account may total at most the package size (Free 20 MB, Starter 200 MB, Maker 1 GB, Growth 10 GB, Pro 25 GB), counted apart from uploaded code files.
- Up to 80% of what is free. On Free that is about 16 MB — enough for a SQLite file or a few dozen pictures.
- Grow, never shrink. Pick what you need now and enlarge it when it fills up.
- Turning it off deletes nothing. Off means the next release does not attach it; the disk itself is kept.
- A release pauses briefly, because the old version must let go of the disk before the new one can use it.
- Commands and scheduled jobs see the same files. The release command, one-off commands and scheduled (cron) jobs all see the app's folder, so a job that cleans old files or resizes pictures works.
If your users' files matter to the business (slips, signed contracts), consider the Backups add-on at ฿99/month: daily database backups plus backups of every project's /bw-data.
Prove that files survive
Do not stop at "the upload worked". Run one full round:
- Upload one test picture on the live site and open it.
- Release a new version (change one word if you like).
- Open the same picture again. If it still shows, it is stored in the right place.
- Delete the test picture.
If it vanished, send your AI: "Uploaded pictures disappear after a release. Find where the code stores files and move them under /bw-data, or use storage_set on that folder."
FAQ
Do I need this for my database?
No. A database created in the project (PostgreSQL, MySQL, MariaDB, MongoDB) keeps its own data. Persistent storage is for the files your app writes, SQLite files included.
/data or /bw-data?
Use /bw-data in new code. Both names are the same folder; a file written through one shows up under the other. /data is the earlier name and still works, but it clashes easily with an app's own folders.
Can I browse or download the files in /bw-data from the console?
Not yet. The Files tab lists the code files you uploaded. To manage your users' files, have the AI add an admin page to the app for viewing and downloading them, open to administrators only.
Is the 16 MB on Free enough?
For a test app, a small SQLite file or resized pictures, yes. If people will upload pictures in earnest, use a paid package and have the AI shrink images before storing them (say, no wider than 1,600 px).
What happens to /bw-data when I delete the project?
The files belong to the project; once it is deleted, the app is no longer there to serve them. If anything matters, get it out first (for example through the admin page from the previous answer), then delete the project.
Summary
An app on BrewInTH restarts from its code on every release, so whatever your users upload must live in persistent storage at /bw-data. Turn it on under App settings, let your AI do it with storage_set, or let Upload & go live do it for you. Pick a sensible size, since it cannot shrink, and test with one extra release. Your users' pictures will stay put.
Start your first project for free →
