Skip to content

Deployment

Two-environment architecture deployed to Cloudflare Workers via GitHub Actions. Two-branch model — develop is the integration/staging branch, main is production.

Environments

StagingProduction
TriggerPush to developPush to main (merge from develop)
Web Workercabal-trading-stagingcabal-trading-production
Indexer Workercabal-indexer-stagingcabal-indexer-production
URLhttps://cabal-trading-staging.cabal-trading.workers.devhttps://cabal.trading
DatabaseSeparate Supabase projectPrimary Supabase project
SolanaDevnet (Raydium), dev treasury walletMainnet (Jupiter), prod treasury wallet
HyperliquidTestnet (api.hyperliquid-testnet.xyz)Mainnet (api.hyperliquid.xyz)
Indexer cronEvery 10 minEvery 5 min

How It Works

  • Feature branches: Branch off develop, PR back to develop. CI runs on PRs to both develop and main.
  • Push to develop: Staging deploys automatically (.github/workflows/deploy-staging.yml) — migrate → deploy app + indexer.
  • Merge develop to main: Deploys production (.github/workflows/deploy-production.yml) — migrate → deploy app + indexer.

Build uses bunx @opennextjs/cloudflare build. Deploy uses bunx wrangler deploy --env <environment>.

Wrangler Environments

Both apps/web/wrangler.toml and apps/indexer/wrangler.toml use wrangler environments ([env.staging], [env.production]). Top-level config is shared; environment-specific config (Hyperdrive, R2, vars, crons) lives under the env sections.

Public vars (NEXT_PUBLIC_PRIVY_APP_ID, SQUADS_VAULT_ADDRESS, etc.) live in wrangler.toml — not in Infisical or GitHub Secrets.

Bare wrangler deploy (no --env) will fail intentionally — prevents wrong-environment deploys.

Secrets Management — Infisical

Secrets are managed via Infisical. Cloudflare Worker secrets are synced automatically via Infisical's native Cloudflare integration — no wrangler secret put calls needed.

How secrets flow

Infisical is the single source of truth. Two integrations push secrets where they're needed:

IntegrationDirectionWhat it does
Cloudflare Worker syncInfisical → CF WorkersPushes secrets directly to running workers. Rotate a DB password in Infisical → workers pick it up without a redeploy.
GitHub Action (OIDC)Infisical → CI runnerInjects secrets into CI at runtime for builds, migrations, E2E tests. Zero secrets stored in GitHub.

For local dev, use infisical run --env=staging -- bun dev (or .env.local for manual setup).

Adding/Updating Secrets

  1. Update the secret in Infisical dashboard (or via infisical secrets set)
  2. Cloudflare Worker sync pushes it to Workers automatically — no deploy needed
  3. CI picks up changes on next run automatically (fetched via OIDC at runtime)
  4. For changes that need a redeploy (e.g., build-time vars), merge to develop or main

Required GitHub Secrets

None. All secrets (including CLOUDFLARE_API_TOKEN) are stored in Infisical and fetched at CI runtime via OIDC.

Infisical Setup

Project structure

/web/
  staging/    — 16 secrets (DATABASE_URL, PRIVY_APP_SECRET, HELIUS_API_KEY, etc.)
  production/ — 16 secrets (same keys, production values)

Cloudflare Worker syncs (4 total)

Infisical env→Cloudflare Worker
staging→cabal-trading-staging
staging→cabal-indexer-staging
production→cabal-trading-production
production→cabal-indexer-production

GitHub Actions (OIDC)

CI workflows use the Infisical/secrets-action GitHub Action with OIDC auth to fetch secrets at runtime. No secrets are stored in GitHub — GitHub's OIDC token authenticates directly with Infisical.

Machine identity: github (ID: 6f2cbe50-29b6-49a2-8f5d-b3d574a601dc), OIDC auth, Viewer role.

Secrets in Infisical (per environment)

SecretPurpose
DATABASE_URLSupabase pooler connection string
PRIVY_APP_SECRETPrivy app secret
PRIVY_AUTH_PUBLIC_KEYP256 authorization public key
PRIVY_AUTH_PRIVATE_KEYP256 authorization private key
HELIUS_RPC_URLHelius RPC endpoint (devnet or mainnet — cluster derived from URL)
HELIUS_API_KEYHelius API key for REST API calls (api.helius.xyz)
HELIUS_WEBHOOK_SECRETHelius webhook verification
JUPITER_API_KEYJupiter API key for swaps
FAL_API_KEYfal.ai for image generation
KIE_API_KEYKie.ai for Sora 2 video generation
KIE_WEBHOOK_SECRETKie webhook verification
BYTEPLUS_API_KEYBytePlus for Seedance video generation
BYTEPLUS_WEBHOOK_SECRETBytePlus webhook verification
R2_ACCOUNT_IDCloudflare account ID for R2 S3-compatible API
R2_ACCESS_KEY_IDR2 API token access key
R2_SECRET_ACCESS_KEYR2 API token secret key
CLOUDFLARE_API_TOKENCloudflare API token (used by wrangler in CI)

Public vars in wrangler.toml (not secrets)

VarPurpose
NEXT_PUBLIC_SITE_URLSite URL for client-side
NEXT_PUBLIC_GA_IDGoogle Analytics (production only)
NEXT_PUBLIC_PRIVY_APP_IDPrivy app ID (public, used client-side and server-side)
SQUADS_VAULT_ADDRESSSquads v4 multisig vault PDA (receives Jupiter/DBC fees)
SQUADS_MULTISIG_ADDRESSSquads multisig program account (for proposal deep-links)
HL_API_URLHyperliquid API URL
HL_WS_URLHyperliquid WebSocket URL
R2_BUCKET_NAMER2 bucket name (cabal-assets-staging / cabal-assets)
R2_PUBLIC_URLR2 public URL for assets

Local Development

With Infisical (recommended)

infisical login                          # One-time auth
infisical run --env=staging -- bun dev   # Start dev server with staging secrets

Without Infisical

cp apps/web/.env.example apps/web/.env.local
# Fill in values manually
bun dev

Manual Setup (one-time)

Before the first staging deploy:

  1. Create a staging Supabase project → get pooler connection string (port 6543)
  2. Create Hyperdrive for staging: bunx wrangler hyperdrive create cabal-staging --connection-string="<staging_pooler_url>" → update ID in apps/web/wrangler.toml
  3. Create staging R2 bucket: bunx wrangler r2 bucket create cabal-assets-staging
  4. Generate a dev treasury Solana wallet (separate from prod)
  5. Seed secrets into Infisical staging environment
  6. Set up Cloudflare Worker syncs in Infisical dashboard
  7. Create machine identity with OIDC auth for GitHub Actions
  8. Add CLOUDFLARE_API_TOKEN to Infisical (both staging and production environments)

Custom Domain

The cabal.trading domain route must be pointed at the cabal-trading-production worker (not the old cabal-trading worker). Update this in the Cloudflare dashboard under Workers → Routes after the first production deploy.

Local Scripts

bun run deploy:staging           # Build & deploy web to staging
bun run deploy:production        # Build & deploy web to production
bun run deploy:indexer:staging   # Deploy indexer to staging
bun run deploy:indexer:production # Deploy indexer to production

Troubleshooting

SymptomCauseFix
wrangler deploy fails (no --env)Missing environment flagUse --env staging or --env production
"Trading not available"SQUADS_VAULT_ADDRESS not setCheck wrangler.toml value
Staging HL agents show zero balancesIndexer queries HL testnetExpected — agents have mainnet HL addresses
Build fails with missing NEXT_PUBLIC_*Not set at build timeThese must be in the workflow env: block, not just wrangler vars
Old cabal-trading worker still runningNot cleaned up after migrationDelete from CF dashboard after verifying new workers work
Secrets not in WorkerInfisical sync not configuredCheck Infisical dashboard → Integrations → Cloudflare

Known Issues

  • No production deploy approval gate: Push to main auto-deploys with no manual approval step and no rollback mechanism. A bad merge goes straight to production.
  • Zero centralized error tracking: No Sentry or equivalent. Errors are only visible in Cloudflare Workers logs (limited retention, no alerting).
  • sync-cli.yml has unpinned actions: Uses actions/checkout@v4 while all other workflows pin to specific commit SHAs.
  • CLI sync workflow broken: The GH_PAT secret has expired. CLI must be synced to the public cabaltrading/cli repo manually until a new PAT is generated and the workflow is updated.
  • Device code cleanup: Expired device authorization codes accumulate indefinitely in the database. No cleanup cron or TTL-based purge exists.