Theonis quietly keeps, sorts, and shares every file in your home — so you don't have to. A self-hosted, Dropbox-like file server built to run on almost nothing: one lightweight process, constant memory regardless of file size, and a clean web UI, all on hardware as modest as a Raspberry Pi.
Most self-hosted file servers assume you have a beefy NAS or a Kubernetes cluster to spare. Theonis assumes the opposite: it's designed from the ground up to sit quietly on a small home server — alongside a dozen other containers — without hogging RAM or spawning worker pools it doesn't need.
- One process, not a fleet. A single asyncio-based Uvicorn worker handles everything. No multi-process pooling, no background workers to babysit.
- Streaming, always. Uploads and downloads are piped in chunks straight between the network socket and disk. Memory usage stays flat whether the file is 1 KB or 10 GB.
- NoSQL metadata, zero server to run. File/folder structure and user accounts live in a single TinyDB JSON file — no separate database process, no schema migrations to manage.
- No build toolchain required. The frontend is vanilla JS and server-rendered Jinja2 templates. Tailwind CSS is compiled from a standalone CLI binary at Docker build time — no Node.js, no npm, no bundler.
- Accounts — invitation-based sign-up (an admin mints a one-shot link; the very first account on an empty server bootstraps itself through a short first-run wizard), session-cookie auth (Argon2 password hashing, signed & versioned sessions), per-user isolated storage.
- Files — drag-and-drop upload (including whole folders), streaming download, rename, move, recursive delete, list/grid views with a live preview for text/PDF/image files, alongside a details panel (size, image dimensions, owner, which disk it landed on).
- Search — one box over everything you can reach, your own files and anything shared with you, narrowed by type, owner, age or size. Save a filter set and it pins to the sidebar; a search is a shareable URL.
- Shelves — Recent, Starred, Shared with me and Trash, each a click away in the sidebar.
- Trash — deleting is undoable. Items rest for 30 days, showing where they came from and how long is left, then the disk reclaims the space. Restore brings a whole folder back, and steps around a name taken in the meantime.
- Version history — re-upload a file under the same name and it revises in place rather than becoming a second copy. The last 10 revisions (or 90 days) stay downloadable and restorable from a timeline, and restoring is itself undoable.
- Bulk actions — multi-select (long-press or click), bulk move/delete, and bulk download as a single generated ZIP (folders included, structure preserved). The action bar docks to the bottom of the screen while a selection is active, staying in reach on mobile.
- Sharing — share any file or folder with one or more registered users at once, picked from a live list, or generate a public tokenized link (with optional expiry). Sharing a folder cascades to everything inside it (subfolders included); a public folder link opens a browsable page. A file shared only through a folder stops being shared the moment it's moved out. No confusing "view vs. download" permission split — a share just grants access.
- Profile — change your username, upload a profile picture, and self-delete your account (which cleanly cascades: your files and every share you're involved in, gone).
- Admin dashboard — disk usage and per-user storage at a glance, a health card reporting what the disks actually say, the tail of the server log without shelling into the container, invitation links to hand out, choose which detected disks Theonis stores new files on (each physical disk shown once), plus the ability to promote another account to admin.
- Deployment-ready — multi-stage Dockerfile (builds on x86_64 and arm64, e.g. a Raspberry Pi),
docker-compose.ymlwith a healthcheck andrestart: unless-stopped, so it survives a reboot on its own.
git clone https://github.com/<your-username>/theonis.git
cd theonis
cp .env.example .env # edit THEONIS_SECRET_KEY and THEONIS_ADMIN_PASSWORD before going further
docker compose up -d --buildThat's it. docker compose build compiles Tailwind CSS from source for whichever CPU architecture you're building on — no Node required — and the app starts as a single, non-root Uvicorn worker.
Open http://<your-server>:8000, log in with the admin credentials from .env, and you're in.
The container runs as a fixed non-root UID/GID 1000. On most single-user Linux hosts that's already your own user's UID, so the bind-mounted ./data directory just works. If your host uses a different UID, fix ownership before the first start:
sudo chown -R 1000:1000 ./dataAll configuration is environment variables, read from .env (see .env.example for the full list with defaults):
| Variable | Default | What it does |
|---|---|---|
THEONIS_SECRET_KEY |
change-me |
Signs session cookies. Change this before exposing the app to anyone. |
THEONIS_ADMIN_USERNAME / THEONIS_ADMIN_PASSWORD |
admin / change-me |
Bootstrap admin account, created on first run. |
THEONIS_DATA_DIR, THEONIS_DB_PATH, THEONIS_STORAGE_ROOT |
data/, data/db.json, data/storage |
Where metadata and the default file content location live. |
THEONIS_STORAGE_SUBDIR |
theonis-storage |
Folder name created under each additional disk you enable in the admin dashboard, so Theonis content stays in its own directory on that mount. |
THEONIS_MAX_UPLOAD_SIZE_BYTES |
10 GiB | Per-file upload cap. |
THEONIS_SESSION_COOKIE_SECURE |
false |
Set to true once Theonis is served over HTTPS (see below). |
THEONIS_SESSION_MAX_AGE_SECONDS |
14 days | How long a login session stays valid. |
THEONIS_LOGIN_RATE_LIMIT_MAX_ATTEMPTS / ..._WINDOW_SECONDS |
5 / 60s | Brute-force login protection. |
THEONIS_REQUIRE_INVITE |
true |
New accounts need an invitation link from an admin. Set to false for open self-registration. |
THEONIS_INVITE_TTL_DAYS |
7 | How long an invitation link stays usable. |
THEONIS_LOG_FILE |
data/logs/theonis.log |
Rotating log file the admin dashboard reads its "Server logs" view from. |
Theonis does not terminate TLS itself — put something in front of it that does. Two common setups:
Reverse proxy (nginx, Caddy, Traefik)
Point your proxy at 127.0.0.1:8000, terminate HTTPS there, and set THEONIS_SESSION_COOKIE_SECURE=true in .env once traffic arrives over HTTPS. Restart the container to apply.
Tailscale (no reverse proxy needed)
If your home server is already on a Tailscale tailnet, you get HTTPS with a real, automatically-renewed certificate for free — no nginx/Caddy required:
# 1. Keep the container reachable only from localhost, not the whole LAN/tailnet
# (in docker-compose.yml):
# ports:
# - "127.0.0.1:8000:8000"
# 2. Expose it over HTTPS on your tailnet (persists across reboots on its own)
tailscale serve --bg --https=8443 http://127.0.0.1:8000
# 3. Tell Theonis requests now arrive over HTTPS
echo "THEONIS_SESSION_COOKIE_SECURE=true" >> .env
docker compose up -dYour file server is now at https://<your-machine>.<your-tailnet>.ts.net:8443 — reachable only by devices on your tailnet, never the public internet, with a certificate a browser actually trusts.
Everything Theonis needs to restore is the ./data directory — it contains both the metadata file and every user's uploaded content. Back it up by copying that directory; no dedicated backup tooling required. Stopping the container first avoids copying db.json mid-write, though the window for a torn read is small since TinyDB rewrites the whole file on each change.
By default Theonis stores everything under THEONIS_STORAGE_ROOT (inside ./data). If you have more than one disk, the admin dashboard lists the writable filesystems Theonis detects (each physical disk shown once) and lets you tick which ones it may use; new uploads land on the enabled disk with the most free space. Un-ticking a disk does not move anything — files already on it stay put and remain downloadable; only new uploads stop going there. (If you want a disk emptied, move its files out through the app before physically detaching it.)
Detection reads the host's mount table, so inside Docker a disk is only offered once it's bind-mounted into the container. To make /mnt/bigdisk available, mount it in docker-compose.yml:
volumes:
- ./data:/srv/theonis/data
- /mnt/bigdisk:/mnt/bigdisk # now selectable in the admin dashboardTheonis writes its files to a theonis-storage/ subdirectory on each enabled disk (configurable via THEONIS_STORAGE_SUBDIR). Back up every enabled disk's theonis-storage/ directory alongside ./data (see below).
The app runs as a single Uvicorn worker by design — do not scale --workers or run multiple replicas against the same data/ directory. TinyDB's JSON storage is not safe for concurrent multi-process writers. Theonis is built for small user bases on modest hardware, not for horizontal scale.
pip install -e ".[dev]"
cp .env.example .env
./scripts/build_tailwind.sh # compiles app/static/css/tailwind.css (no Node required)
pytest
uvicorn app.main:app --reloaddocs/TECHNICAL_SPEC.md— the living technical spec: design rationale, every notable decision, and the full API surface.
FastAPI · Uvicorn · TinyDB · Jinja2 · vanilla JS · Tailwind CSS · Argon2 · Docker
MIT — do what you like with it.