Skip to content

Why the three-tier deployment model

Scani ships one set of binaries (api, worker, data-provider, frontend-app) that runs in three shapes:

  • Tier 1 — fully self-hosted. Everything on your hardware, including the data-provider.
  • Tier 2 — semi-managed. api + worker + frontend-app on your side; data-provider provided by a hosted endpoint.
  • Tier 3 — fully managed. Someone else runs the whole stack.

Two environment variables switch tiers:

  • SCANI_CLOUD_URL — where to send object storage, email, OG-metadata and token-search calls.
  • SCANI_CLOUD_API_KEY — the bearer the api + worker present.

Per-tier forks of the codebase: a self-host build, a managed-service build, a SaaS build. Each with its own auth model, its own deployment shape, its own feature set.

Forks rot. Three codebases means three places to land every feature, three places to fix every bug, three CI matrices, three chances for security findings to apply to two of three forks. The team is small. The fork tax is unaffordable.

The natural seam already existed. Every third-party call (pricing, AI, blockchain RPC, email) was already centralised on the data-provider service for testability and rate-limit isolation. Once the seam is named, the tier choice is just “which endpoint do api + worker call?”. No fork required.

User credentials must stay on the user’s side. Exchange API keys, brokerage tokens, screenshot blobs — these are sensitive. They live on the api, which the user controls (Tier 1) or which their operator runs (Tier 2). The data-provider only sees requests that don’t carry user secrets: an object-storage read or write, a rendered email, a URL to fetch OG metadata for, a symbol to search. The tier seam is also the credential boundary — no decrypted integration credential ever crosses it.

AspectTier 1Tier 2Tier 3
api runs…On your hardware.On your hardware.Hosted.
worker runs…On your hardware.On your hardware.Hosted.
data-provider runs…On your hardware.Hosted endpoint.Hosted endpoint.
User integration creds (exchange keys, brokerage tokens)On your api.On your api.Hosted.
Provider keys (CoinGecko, OpenAI, Etherscan)You set them, read by your api + worker.You set them, read by your api + worker.Operator sets them.
Object storage, email, OG metadata, token searchYour data-provider.Hosted data-provider.Hosted data-provider.
What’s reachable from the public internetJust frontend-app.Just frontend-app.All hosted.
  • One codebase, three deployment shapes. Every PR ships to all three tiers simultaneously.
  • The Tier-1→Tier-2 migration is a config change. Re-point SCANI_CLOUD_URL at the hosted endpoint; provide the issued SCANI_CLOUD_API_KEY; restart api + worker. Your data stays on your side.
  • Provider keys are centralised for the calls that cross the seam. A small operator running 50 users provides one bucket and one mail transport for all of them. The intent was for pricing and AI keys to work the same way; the caution above is why they do not yet.
  • Tier 1 keeps every credential on one host. No hosted service is involved at all, and the OSS distribution sends no telemetry. Note this is not an air gap: pricing, chain and AI calls still go out to the upstreams whose keys you set, from your own api and worker.
  • The data-provider is a separate service to run. In Tier 1 it is a sidecar on the same compose network; the operational cost is trivial.
  • Two env vars have to match. SCANI_CLOUD_API_KEY on api + worker must equal DATA_PROVIDER_API_KEY on the data-provider. In Tier 1 single-tenant, both are seeded with the same value in .env.example.
  • A “merge the api and the data-provider for Tier 1 only” shortcut. The whole point of the seam is that it doesn’t go away in any tier — that’s what makes the contract testable end-to-end.
  • Per-tier feature flags that change product behaviour. The product is the same; only the deployment shape differs.