Production topology
The app runs as a single, always-on instance on Fly.io: a shared-CPU machine with 512 MB of memory, a persistent volume mounted at/data, and auto-stop disabled. The always-on requirement is functional, not incidental: storefront trackers send heartbeats every few seconds and the counter widget polls continuously — a sleeping machine would freeze live counts.
SQLite is the production database, stored on the persistent volume. This is a deliberate trade-off: it keeps the stack simple and honest for a single-instance app, and it defines the scaling boundary described below.
Docker image
The image isnode:22-alpine with OpenSSL. It installs full dependencies (build tooling lives in devDependencies), builds the app, prunes dev dependencies, and on boot runs:
Deploy flow
Environment variables
Operational constraints
- Single instance only, for now. Rate limiting, view-dedupe locking, and the order-sync throttle are in-memory and per-process, and SQLite allows one writer. Running multiple instances requires moving rate limiting and dedupe locks to a shared store (e.g. Redis) and the database to a multi-writer engine. The rate limiter documents this boundary in code.
- The scheduler lives inside the web process. Cron jobs run on the same machine as request handling. Scaling out means either keeping one instance or splitting the scheduler into a dedicated worker with the scheduler flag inverted on the web machines.
- Backups = the database file. Back up the SQLite file on the persistent volume on a schedule; because rollups and retention pruning rewrite it, take backups from a quiesced moment (e.g. shortly after the nightly maintenance window) or use SQLite’s backup API.
API version alignment
The application code pins its Admin API version independently of the app configuration file’s webhook API version. When upgrading either, check both and keep them aligned — a mismatch means webhooks are delivered in one API version while the app queries in another.Post-deploy verification
1
App loads in admin
Open the app from a store’s admin; the dashboard renders with the navigation menu.
2
Proxy smoke test
Send a correctly signed request to the app proxy stats endpoint and expect
200 with a JSON count; an unsigned request must return 400. (Test helpers reproduce the signing algorithm exactly.)3
Webhooks flowing
Place a test order on a store with order webhooks enabled and confirm the order webhook is received in logs; check product and inventory webhooks after an edit.
4
Scheduler firing
Confirm insight generation and order catch-up logs appear on their 30-minute cadence, and that the nightly maintenance window (rollups, pruning) runs.