Skip to main content
Production is split by what each part needs. The site is static, so it lives on Cloudflare’s edge. The API and mock RPC are a small Node service, so they run on a VPS. Both are deployed by one GitHub Actions workflow after CI passes on main: merge, and a few minutes later everything is running that commit.

What runs where

The DNS zone is on Cloudflare. The site’s records are created by Cloudflare when the Worker’s custom domains are attached; api and rpc are created by the workflow (deploy/dns.mjs) as DNS-only records, so nginx on the VPS terminates TLS itself and sees real client addresses for rate limiting.

The site on Cloudflare

wrangler.jsonc deploys dist/ as static assets with no Worker script:
  • not_found_handling: single-page-application, so every path serves the app;
  • custom domains istravex.com and www.istravex.com, with workers.dev and preview URLs off;
  • public/_headers (copied into dist/) sets HSTS, nosniff, a referrer policy, X-Frame-Options: DENY, and a one-year immutable cache on Vite’s fingerprinted /assets/*. Everything else revalidates on every request.
CI runs wrangler deploy --dry-run on every push, so a broken config fails before merge.

The VPS is shared

The VPS also runs other, unrelated sites behind its own nginx, so the deploy is careful to stay inside its own footprint: Every nginx change is checked with nginx -t before a graceful systemctl reload nginx; if the check fails, the previous istravex-api.conf is restored and nothing is reloaded.

The pipeline

.github/workflows/deploy.yml runs when CI completes successfully on main, or manually from the Actions tab. Its three jobs run in parallel where they can.
1

Site → Cloudflare

npm run build, validate with wrangler deploy --dry-run, then wrangler deploy --message <commit>. The first deploy attaches the custom domains, replacing any placeholder records at those names. A smoke test fetches https://istravex.com/.
2

API image → GHCR

Built from the Dockerfile and pushed to ghcr.io/hackcat29/istravex-api, tagged with the commit SHA and latest.
3

API → VPS

Upserts the api and rpc DNS records, then over SSH (strict host-key checking against a pinned known_hosts) copies deploy/ to /opt/istravex and runs deploy/remote.sh. That script logs in to GHCR with the workflow’s short-lived token, pulls the image for this commit, and runs docker compose up -d --wait, which only returns once the API’s health check passes. It then installs the nginx site. On the first deploy it serves the ACME challenge over HTTP, gets a certificate from certbot (webroot, renewed by the host’s certbot timer with a reload hook), and switches to HTTPS. A smoke test then requires https://api.istravex.com/v1/health to report the commit just deployed, and the RPC to report healthy.
Deploys are serialised (concurrency: deploy-production), so two merges in quick succession deploy in order. Each part is skipped, not failed, until it’s configured.

One-time setup

1

Cloudflare API token

In the Cloudflare dashboard, My Profile → API Tokens → Create Token, start from the Edit Cloudflare Workers template, add Zone → DNS → Edit, and limit it to your account and the istravex.com zone. Store it in GitHub:
npx wrangler whoami prints the account ID.
2

VPS access

Generate a key just for deploys, authorise it on the server, and pin the host keys:
Compare the scanned fingerprints (ssh-keygen -lf known_hosts) with the server’s own (ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub, from the provider’s console) before trusting them.
3

Repository secrets and variables

Then delete the private key from your machine.
4

Server prerequisites

Docker with the Compose plugin, nginx, certbot (with an ACME account registered), and ports 80 and 443 open. The deploy doesn’t install or reconfigure any of these, since other sites depend on them.
5

Docs domain

See Docs. In Cloudflare, keep the docs CNAME DNS-only: Mintlify issues its own certificate.

Operating it

Site. npx wrangler deployments list shows recent deploys; npx wrangler rollback returns to the previous one instantly. API.
Roll back by re-running the Deploy workflow on an earlier commit, or on the server:
Resources. The API holds corpus metadata in memory and decompresses at most four ledgers at a time; it runs comfortably in under 200 MB, and the container is capped at 512 MB so it can’t crowd out the server’s other services. Container logs rotate at 5 × 10 MB.