Self-hosting the sync server
The server is a small FastAPI + Postgres + Caddy stack. Any €4/month VPS runs it; it only shuffles ciphertext, so 2 vCPU / 4 GB is generous. Disk is the only resource that really grows: budget roughly 1 GB per heavy user (encrypted PDFs and, where enabled, project files; experiment outputs stay local except what remote runs ship back, capped at 100 MB per file).
https://sync-dev.tramea.org (registration is gated by your
invite code).Deploy in five steps
# 1. a VPS (Ubuntu 24.04) + a DNS A record: sync.yourdomain.org → VPS IP
# 2. on the VPS
apt update && apt install -y docker.io docker-compose-v2 ufw
ufw allow 22 && ufw allow 80 && ufw allow 443 && ufw --force enable
# 3. copy the server/ directory, then configure
cp .env.example .env
# ROS_SERVER_SECRET=$(openssl rand -base64 48)
# POSTGRES_PASSWORD=$(openssl rand -hex 24)
# SERVER_DOMAIN=sync.yourdomain.org
# ROS_SERVER_INVITE_CODE=… # optional: invite-only registration
# 4. launch — Caddy fetches a Let's Encrypt certificate automatically
docker compose up -d --build
# 5. verify
curl https://sync.yourdomain.org/v1/health
Invite-only registration
Set ROS_SERVER_INVITE_CODE in .env to
require an invite code at registration — hand the code to the people
you invite. Leave it empty for open registration.
Optional: AI relay and lab-local AI
Both are off until you configure them; sync never depends on either.
- Hosted AI relay — to offer your users the
"plan credits" assistant provider, set
ROS_SERVER_AI_KEY(and optionallyROS_SERVER_AI_PROVIDER/ROS_SERVER_AI_MODEL; the default is Anthropic with a Haiku-class model) in.env. Questions are metered per account per month, forwarded to the provider, and never stored or logged — this is the server's only plaintext route, and users must explicitly select it. Without the key, the endpoint answers 503 and the provider simply doesn't appear in apps. - Lab-local AI — point a lab's members at an
OpenAI-compatible model on the lab's own hardware (Ollama, vLLM, …):
org-ai ORG_ID http://ai.lab.local:11434/v1 --model mistral-small(see Administration below). Members' apps pick it up as the "Lab server (local model)" provider and default to it; prompts then never leave the lab network. Pass an empty endpoint to disconnect.
Administration
A small admin CLI runs inside the app container:
docker compose exec app python -m ros_server.admin stats
docker compose exec app python -m ros_server.admin accounts
docker compose exec app python -m ros_server.admin backup -o backup.tar.gz
docker compose exec app python -m ros_server.admin set-plan you@lab.org pro
docker compose exec app python -m ros_server.admin create-org my-lab --seats 20
docker compose exec app python -m ros_server.admin org-add <org-id> member@lab.org
docker compose exec app python -m ros_server.admin org-ai <org-id> http://ai.lab.local:11434/v1
docker compose exec app python -m ros_server.admin team-create <org-id> "Team Memory"
docker compose exec app python -m ros_server.admin team-add <team-id> lead@lab.org --lead
docker compose exec app python -m ros_server.admin set-org-role pi@lab.org admin
docker compose exec app python -m ros_server.admin org-remove member@lab.org --revoke
The last one is the departure flow: it takes a member out of the
organization, and with --revoke also removes them from
every org-owned space and team — the server refuses their further
pulls. Their personal spaces and local data are untouched.
Everything it touches is metadata — plans, seats, usage counts, backups. It cannot read anyone's content; there is no key to read it with.
Operations
- Backups:
docker compose exec db pg_dump -U ros ros > backup.sqlweekly, plus the blob volume. Even total server loss is survivable — every device holds plaintext. - Updates: copy the new
server/,docker compose up -d --build. - Accounts: users manage themselves — passphrase change, recovery, device revocation and account deletion are all self-service in the app.