Persistent Storage

How /app/data works, seeding files, and when to use a database instead

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.

Python
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.

Python — seed on first run
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": []}))
Don't use 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.

clouderized.yaml
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

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.

Python · SQLite (persistent)
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.