How it works
Every Clouderized app gets a persistent volume mounted at /app/data automatically — no configuration needed.
Anything your app writes there survives redeploys, restarts, and container rebuilds. Files written anywhere else in the container filesystem are ephemeral and lost when the container is replaced.
Survives redeploys
Push a new version, rebuild the image — your data at /app/data is untouched.
Backed up automatically
Your /app/data directory is included in the daily backup to offsite storage.
Outside /app/data is ephemeral
Files written to /tmp, /var, or anywhere else disappear on redeploy.
Using /app/data in your app
Point file writes to /app/data. Treat it like any local directory — create subdirectories, write files, append logs.
import os
from pathlib import Path
DATA_DIR = Path("/app/data")
DATA_DIR.mkdir(parents=True, exist_ok=True)
uploads = DATA_DIR / "uploads"
uploads.mkdir(exist_ok=True)
# Write a file
(DATA_DIR / "config.json").write_text('{"key": "value"}')
# Read it back
content = (DATA_DIR / "config.json").read_text()
Seeding initial files
/app/data is a bind mount — files COPY'd there in your Dockerfile are hidden at runtime by the mount.
The correct pattern is to seed on first run: check if the file exists, write it if not.
from pathlib import Path
import json
DATA_DIR = Path("/app/data")
config_path = DATA_DIR / "config.json"
if not config_path.exists():
DATA_DIR.mkdir(parents=True, exist_ok=True)
config_path.write_text(json.dumps({"initialized": True, "items": []}))
COPY to /app/data in your Dockerfile. The bind mount replaces the entire directory at container start — anything copied there by the image build will not be visible at runtime.
Additional mount points
Need a volume at a different path — for example, a legacy app that writes to /var/app/uploads?
Declare extra volumes in clouderized.yaml and the operator will apply them.
name: myapp
port: 8080
tier: pro
volumes:
- name: uploads
mount: /var/app/uploads
- name: cache
mount: /var/cache/myapp
Each named volume is persisted separately. All volumes survive redeploys. Contact the operator to add or change volume declarations.
Storage vs database — which to use?
| Use case | Use /app/data | Use a database |
|---|---|---|
| User-uploaded files (images, PDFs) | Yes | No — large blobs don't belong in SQL |
| Structured records (users, orders, posts) | No | Yes — queries, joins, integrity |
| Single-app embedded database (SQLite) | Yes — store the .db file in /app/data | Only if you need multi-app access |
| Config files, seed data, feature flags | Yes — write once, read often | Only if you need dynamic updates from multiple sources |
| Shared data between multiple apps | No — each app has its own volume | Yes — a shared DB is the right bus |
| Generated reports, exports | Yes — write the file, serve it | No |
Managing files from the dashboard
The Clouderized dashboard includes a built-in file manager for your app's /app/data directory — no SSH or command line needed.
Browse & download
Navigate directories, preview files, and download individual files directly from the browser.
Upload files
Upload config files, seed data, or any static asset directly into /app/data without a redeploy.
Create directories & delete files
Organise your storage, clean up old exports, or set up an initial folder structure before your app first runs.
Log in at dash.clouderized.com with your Gitea credentials → open your app → Files tab.
Limitations
-
—
No shared volumes between apps. Each app's
/app/datais isolated. If two apps need the same files, use a database or ask the operator about a custom setup. - — Storage is capped by your slot tier. Starter slots include 4 GB total (app data + container image + git repo); Pro slots include 16 GB. Avoid storing very large binary datasets — use object storage (S3/R2) for that scale.
- — Backups are daily snapshots. Your data is backed up daily. There is no per-file point-in-time recovery — restore is at the snapshot level.
Using SQLite in /app/data
SQLite is a great fit for single-app workloads. Store the database file in /app/data so it persists across redeploys.
import sqlite3
from pathlib import Path
db_path = Path("/app/data") / "app.db"
db_path.parent.mkdir(parents=True, exist_ok=True)
conn = sqlite3.connect(db_path)
conn.execute("CREATE TABLE IF NOT EXISTS events (id INTEGER PRIMARY KEY, name TEXT, ts INTEGER)")
conn.commit()
If your app grows and you need concurrent writes from multiple processes or shared access from another app, migrate to a Starter DB (MariaDB or PostgreSQL) — see Connecting to Databases.