Skip to main content

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 is node:22-alpine with OpenSSL. It installs full dependencies (build tooling lives in devDependencies), builds the app, prunes dev dependencies, and on boot runs:
Deploys are therefore migration-driven: every boot applies pending migrations before serving traffic.

Deploy flow

This registers the current app version: access scopes, webhook subscriptions, the app proxy mapping, and theme extension assets. After changing app configuration (scopes, webhooks, proxy), a deploy is what makes the change live for installed stores.

Environment variables

Never commit secret values. The repository’s convention is that secrets exist only in the deployment environment or the Shopify CLI session — see Security.

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.