VPS foundation (live)
What is actually deployed today: self-hosted Convex, the SSR frontend, FastAPI, Redis, monitoring and Portainer — in Docker behind the existing Traefik, isolated and portable.
Phase 1 “Foundation” is live on the Hostinger VPS: self-hosted Convex, the SSR frontend, a FastAPI service, Redis, a Prometheus/Grafana/Loki monitoring stack and Portainer — every component a container, behind the existing Traefik, isolated from the other projects on the box and portable to another host.
Topology
Internet | HTTPS (Let's Encrypt - mytlschallenge) v Traefik (root-traefik-1, shared, network: root_default) | |- clawlas.com ........... frontend (SSR :3000) |- convex.clawlas.com .... convex-backend API (:3210) |- convex-site.clawlas.com convex-backend HTTP actions (:3211) - Better Auth |- convex-dashboard.* .... convex-dashboard (basic-auth) |- grafana.* ............. grafana '- portainer.* ........... portainer internal networks (no host ports, isolated from other projects): clawlas_application frontend <-> api clawlas_data convex-backend - api - redis - backup clawlas_monitoring prometheus - grafana - loki - promtail - node-exporter - cadvisor clawlas_execution (reserved for future execution workers)
Access URLs
| Surface | URL | How to sign in |
|---|---|---|
| Web app | clawlas.com | Email + password (Sign up / Sign in) |
| Convex dashboard | convex-dashboard.clawlas.com | Traefik basic-auth, then the deployment URL (convex.clawlas.com) + admin key |
| Grafana | grafana.clawlas.com | Grafana login (admin) — needs its DNS A record |
| Portainer | portainer.clawlas.com | Portainer login — needs its DNS A record |
| Convex API / site | convex.clawlas.com · convex-site.clawlas.com | Programmatic — no UI (used by the app + the CLI) |
deploy/compose/.env on the VPS — never committed here.Services
| Service | Role | Network | Public host |
|---|---|---|---|
| convex-backend | Self-hosted Convex (SQLite) | root_default · data | convex(-site).clawlas.com |
| convex-dashboard | Convex admin UI | root_default | convex-dashboard.clawlas.com |
| frontend | TanStack Start SSR | root_default · application | clawlas.com |
| api | FastAPI (health/ready scaffold) | application · data | — |
| redis | Valkey cache / event transport | data | — |
| grafana | Dashboards | monitoring · root_default | grafana.clawlas.com |
| prometheus | Metrics | monitoring | — |
| node-exporter / cadvisor | Host + container metrics | monitoring | — |
| loki / promtail | Logs + shipping | monitoring | — |
| portainer | Docker management UI | monitoring · root_default | portainer.clawlas.com |
| backup | restic snapshots (profile-gated) | data | — |
Not in this phase
No trading logic, workers, collectors or execution services yet — those arrive in later phases. The clawlas_execution network is created now but unused.
Container reference
Every container, its job and its key specs (drawn from deploy/compose/docker-compose.yml). All run restart: unless-stopped with no-new-privileges; resource limits are env-tunable; no service publishes a host port.
convex-backend
Self-hosted Convex backend: reactive DB, queries/mutations/actions, the Better Auth component and HTTP-action routes (SQLite).
Why: Owns the app data and auth on the VPS — no per-request Cloud bill, fully portable.
Specs: Ports 3210 (API) + 3211 (HTTP actions) · networks public + data · volume convex_data · 1 CPU / 1 GB · healthcheck /version
convex-dashboard
Convex admin UI (data browser, functions, logs, env). A browser app reaching the backend via the public URL plus the admin key.
Why: Operate and inspect self-hosted Convex (browse data, run functions, edit env) without the Cloud console.
Specs: Port 6791 · network public · depends on convex-backend · 0.5 CPU / 512 MB · Traefik basic-auth
frontend
TanStack Start SSR web app and the /api/auth/* proxy to Convex, served by the Node host server.mjs.
Why: Serves the app from the VPS behind the same proxy, not a separate hosting platform.
Specs: Port 3000 · networks public + application · built from apps/web/Dockerfile · 0.5 CPU / 512 MB · non-root
redis
Valkey: in-memory cache today, the Redis-Streams event transport for later phases.
Why: A fast cache now, and the async event boundary the worker fleet will need — kept stable from day one.
Specs: Port 6379 (internal) · network data · volume redis_data · password-protected · 256 MB · healthcheck ping
api
Internal FastAPI scaffold: /health (liveness) and /ready (pings Redis). No business logic yet.
Why: Establishes the Python/TypeScript boundary early; the future interface between Convex/web and the trading and AI workers.
Specs: Port 8000 (internal) · networks application + data · depends on redis · 0.5 CPU / 512 MB
prometheus
Metrics time-series DB; scrapes node-exporter, cadvisor and itself.
Why: Measure resource use on a shared VPS so the stack can be tuned and never starves the other projects.
Specs: Port 9090 · network monitoring · volume prometheus_data · 15-day retention · 384 MB
node-exporter
Host metrics: CPU, memory, disk, network and load for the VPS.
Why: See host-level pressure (CPU, memory, disk) to size and alert.
Specs: Port 9100 · network monitoring · pid host, mounts / read-only · 128 MB
cadvisor
Per-container CPU/memory/network/disk metrics.
Why: Attribute usage per container, to verify the resource isolation actually holds.
Specs: Port 8080 · network monitoring · privileged, read-only host mounts · 256 MB
loki
Log aggregation store (single-binary, filesystem backend).
Why: Centralize logs for debugging without SSH-ing into each container.
Specs: Port 3100 · network monitoring · volume loki_data · 7-day retention · 256 MB
promtail
Tails container JSON logs and ships them to Loki.
Why: Gets every container log into Loki automatically.
Specs: Port 9080 · network monitoring · mounts container logs read-only · 128 MB
grafana
Dashboards and log exploration over Prometheus + Loki (auto-provisioned datasources).
Why: One place to watch metrics and logs — the day-to-day operability surface.
Specs: Port 3000 · networks monitoring + public · volume grafana_data · 256 MB · own login
portainer
Docker management UI: containers, logs, volumes, stacks. Coexists with Traefik, no Swarm.
Why: Manage the stack from a UI without SSH; chosen over Dokploy because it coexists with the existing Traefik (no Swarm).
Specs: Port 9000 · networks monitoring + public · docker.sock + portainer_data · 256 MB · own login
backup
Scheduled encrypted restic snapshots of every clawlas_* volume, off-host. Profile-gated.
Why: The off-host snapshot is the disaster-recovery and lift-and-shift unit — the portability guarantee.
Specs: Profile backup · network data · all volumes read-only · daily 03:00, 7-day retention · 256 MB
Networks and isolation
The whole stack is one Compose project named clawlas, so every container, volume and network is prefixed clawlas_* — no collision with the other projects on the shared VPS. Only public-facing services join the external root_default network; everything else sits on internal: true networks with no route to the internet and no published host ports. Each service has CPU and memory limits so Clawlas can never starve its neighbours.
- project name: clawlas
- named volumes (clawlas_*)
- internal-only networks
- per-service cpu/mem limits
- no published host ports
- Traefik-only ingress
Convex and the three origins
Convex is self-hosted (SQLite) and reached over three distinct public origins that are not derivable from one another — the browser and Better Auth need all three:
| Origin | Purpose |
|---|---|
| convex.clawlas.com | API / cloud origin (:3210) — VITE_CONVEX_URL |
| convex-site.clawlas.com | HTTP actions (:3211) — Better Auth routes, the /api/auth/* proxy target |
| clawlas.com | the app URL — Better Auth baseURL (Convex-side SITE_URL secret) |
Better Auth runs as a Convex component; the frontend proxies /api/auth/* to the convex-site origin. Secrets (BETTER_AUTH_SECRET, SITE_URL) live on the Convex instance, never in an env file.
Frontend
The TanStack Start build emits a Web fetch handler but nothing that listens, so a small Node host — apps/web/server.mjs (@hono/node-server) — serves the static client assets and falls through to SSR. The image is multi-stage with a self-contained production node_modules (pnpm deploy) and runs non-root. Public VITE_* values are baked into the client at build time and also read from process.env at runtime for SSR.
Monitoring
Prometheus scrapes host metrics (node-exporter) and per-container usage (cadvisor); Promtail ships container logs to Loki; Grafana reads both via auto-provisioned datasources. The whole stack is on the isolated clawlas_monitoring network.
Backups and portability
- 1Encrypted off-host snapshotsThe backup service (restic, behind the `backup` compose profile) snapshots every clawlas_* volume to an off-host repository on a daily schedule.
- 2Lift-and-shiftMoving to a new VPS = git clone + restore secrets from the Bitwarden note + deploy/scripts/restore.sh (repopulate volumes) + docker compose up -d + repoint DNS.
- 3Secrets stay out of snapshotsThe .env (root-600) never leaves the box and is not in the snapshot — the Bitwarden secure note is the secrets backup.
deploy/README.md; the architecture rationale is in docs/VPS_ARCHITECTURE.md.