ProjectR Docs
Getting Started

Environments

Convex deployments, env files, and deploy keys for dev, staging, and production.

The three environments

EnvConvex deploymentConvex cloud URLHTTP URL
Productionprecious-rat-335appsapi.theraw.com.myappsbackend.theraw.com.my
Stagingagile-corgi-641agile-corgi-641.convex.cloudagile-corgi-641.convex.site
Developmentagreeable-canary-738 (personal dev)agreeable-canary-738.convex.cloudagreeable-canary-738.convex.site

Staging and production are both prod-type deployments in the Convex project tipeer:theraw. Staging did not exist at migration time — it was added 2026-08-25 across backend, dashboard, and Vercel.

The dashboard maps to environments through Vercel: production builds from main (prod backend), previews from staging (staging backend). Mobile selects its backend by APP_ENV (development/staging/production).

Deploying the backend

cd packages/backend
bun run deploy:staging   # uses CONVEX_DEPLOY_KEY from .env.staging.local
bun run deploy:prod      # bare `convex deploy` — targets production

The deploy key is the redirect mechanism: --env-file alone does not redirect convex deploy, only CONVEX_DEPLOY_KEY does. The staging key lives in packages/backend/.env.staging.local (gitignored).

To copy env vars between deployments: bun run env:sync-staging runs scripts/sync-convex-env.sh, which copies every var from dev to staging, skips JWKS (each deployment signs with its own key), and hard-refuses if the target is production.

Env files by package

All secret-bearing files are gitignored; only apps/dashboard/.env.example is tracked.

packages/backend

  • .env.local — local convex dev config: CONVEX_DEPLOYMENT, CONVEX_URL, CONVEX_SITE_URL, BETTER_AUTH_SECRET, SITE_URL, plus integrations (iSMS ISMS_USERNAME and ISMS_PASSWORD, RESEND_*, SHOPIFY_*, R2_*, scanner secrets).
  • .env.staging.local — staging CONVEX_DEPLOYMENT + CONVEX_DEPLOY_KEY.
  • .env.production.local — production CONVEX_DEPLOYMENT.

Runtime env vars on each deployment are set with bunx convex env set. Required: BETTER_AUTH_SECRET, SITE_URL (must be the .convex.site endpoint, not the marketing domain — it drives OAuth redirects), JWKS, plus the integration credentials (Shopify, R2, Resend, iSMS, Google/Apple). ALLOW_TEST_PHONE_OTP exists for non-prod only — see Runbooks.

apps/dashboard

  • .env — base defaults (NEXT_PUBLIC_SITE_URL, Sentry disabled flag, Shopify store handle).
  • .env.development / .env.staging / .env.production — NEXT_PUBLIC_CONVEX_URL + NEXT_PUBLIC_CONVEX_SITE_URL per environment.
  • .env.local — personal overrides (e.g. pointing local dev at the personal dev deployment).
  • .env.sentry-build-plugin — the Sentry org token. The build plugin loads it, but only a Vercel build (Production or Preview, where Vercel holds the same token) creates a release or uploads source maps.

Vercel specifics: the project's root directory is apps/dashboard, region sin1, and vercel.json pins installCommand: "npx -y bun@1.4.0 install --frozen-lockfile" because Vercel picks Bun from the lockfile format and its container lags.

apps/mobile

One file per APP_ENV: .env.development, .env.staging, .env.production — all gitignored, loaded by env.js (zod-validated; see Mobile Environment). Same Convex deployment/URL trio, plus app identity, Shopify storefront token, Google client IDs, Sentry DSN, and MMKV obfuscation key.

The most dangerous edge

One Shopify store is shared by every environment. SHOPIFY_ADMIN_ENDPOINT and its token are identical across dev, staging, and production, so every write path (voucher codes, webhook subscriptions, catalog syncs) targets the live store.

SHOPIFY_WRITES_ALLOWED is the guard. adminRequestWithMetadata in convex/shopify/shopifyClient.ts throws SHOPIFY_WRITES_DISABLED before it sends any Admin API mutation unless the deployment sets the variable to 1, and mutations that would only queue a Shopify write check shopifyWritesAllowed() first (convex/helpers/shopifyWrite.ts). The helper's comment names prod and staging as the deployments that set it. On any deployment that does, treat every Shopify write as production-impacting. Storefront cart calls are not gated.

On this page