Two free hosts, no credit card on either side, indefinite free tier on both. Total elapsed time ≈ 15 minutes.
| Layer | Platform | URL pattern |
|---|---|---|
| API server (Bun + Fastify + 0G SDK) | Render Web Service (free) | https://chainshield-api.onrender.com |
| Frontend (Astro static) | Cloudflare Pages (free) | https://chainshield.pages.dev |
| Keep-warm pinger | UptimeRobot (free) | (out-of-band) |
Why this combination:
- Render is the only $0 / no-CC option whose free tier permits the raw outbound TCP that the 0G storage SDK needs (port 5678 to storage nodes). Cloudflare Workers / Vercel Functions / Netlify Functions are all HTTP-only egress and break the 0G upload path — they're disqualified for the API.
- Cloudflare Pages is the only static host with truly unlimited bandwidth and unlimited requests on the free tier. Vercel and Netlify both meter at 100 GB/mo.
- UptimeRobot keeps Render's free tier from sleeping, mitigating the 30-50s cold-start penalty during a demo.
Skip deployment entirely if you only need the project running locally —
docker compose up --buildis the path for that. This guide is for putting a public URL in front of the project so anyone (e.g. ETHGlobal judges) can hit it without cloning the repo.
- The repo is on GitHub at
https://github.com/AnkanMisra/ChainShield. - You have a funded 0G testnet wallet — see the
Funding the 0G walletsection in the README. - You have a KeeperHub API key (org-level, see https://app.keeperhub.com/settings/api-keys).
- (Optional) Discord webhook URL for the notification channel.
You will paste those three secrets into the Render dashboard once. They never leave Render and are not committed to the repo.
Render reads render.yaml at the repo root and provisions everything declaratively.
- Open https://render.com and sign in with GitHub. No credit card required for the free tier.
- Click New + → Blueprint.
- Pick the
AnkanMisra/ChainShieldrepo. Render readsrender.yaml, shows you a single service namedchainshield-api, and lists the env vars. - Three env vars are flagged as needs value (the
sync: falseones in the Blueprint):ZERO_G_PRIVATE_KEY— paste your funded Galileo testnet keyKEEPERHUB_API_KEY— paste your KeeperHub org keyNOTIFY_DISCORD_WEBHOOK— paste a Discord webhook URL, or leave blank to disable
- Click Apply. Render builds the Docker image (≈ 3-4 min for the first build, ≈ 1 min on subsequent pushes via the Bun layer cache) and brings the service up.
- Note the service URL — something like
https://chainshield-api.onrender.com. This is your API base. - Health check:
curl https://chainshield-api.onrender.com/health→{"status":"ok"}.
Auto-deploy is on by default — every push to main triggers a fresh build. Stop a deploy mid-flight from the dashboard if needed.
- Render clones the repo at
main, readsDockerfile, builds the multi-stage Bun + Alpine image. - Boots the container with the env vars from the dashboard (the Blueprint ones plus the three secrets).
- Polls
/healthuntil it returns 200, then flips traffic to the new revision. - The container listens on
0.0.0.0:8787; Render's edge fronts it with TLS on port 443. - The free Web Service tier sleeps after 15 min of zero traffic. The next request takes 30-50s while the container resumes. UptimeRobot (step 3) prevents this during demos.
- Open https://dash.cloudflare.com and sign in. No credit card required for Pages.
- Workers & Pages → Create → Pages tab → Connect to Git.
- Authorize the GitHub app for the
AnkanMisra/ChainShieldrepo. - Setup builds and deployments:
- Production branch:
main - Framework preset: Astro
- Root directory (advanced):
web← important — Cloudflarecds here before the build runs - Build command:
bun install && bunx --bun astro build - Build output directory:
dist← relative to the root directory, i.e.web/distfrom the repo root
- Production branch:
- Expand Environment variables → Production scope. Add two:
PUBLIC_API_BASE= the URL Render gave you, e.g.https://chainshield-api.onrender.comBUN_VERSION=1.3.13← Bun is not preinstalled on Pages runners; this env var opts in to it and pins the patch Repeat both under the Preview scope so branch deploys also build with Bun.
- Save and Deploy.
The first build takes ≈ 90 s. The BUN_VERSION env var is the trigger for Cloudflare's runner to install and use Bun (without it, the runner falls back to npm and Astro's astro build would run under Node 20, which is too old for Astro 6). The bunx --bun prefix on the build command additionally forces Astro onto Bun's runtime as a belt-and-braces guarantee.
After the build completes you get a URL like https://chainshield.pages.dev. Open it. Click Quick demo. Click any preset on the Evaluate form. You should see the working state, the elapsed timer, then the verdict + the lime 0G anchor pill.
Every push to a non-main branch gets its own preview URL like https://abc123.chainshield.pages.dev. The Render API allows these because WEB_ORIGIN in render.yaml includes a regex that matches the full *.chainshield.pages.dev family.
Render's free tier sleeps after 15 min idle. A free cron-job.org ping every 14 min (just under the sleep threshold) keeps the container live during judging.
- Sign up at https://cron-job.org (free, no card).
- Create cronjob:
- Title:
chainshield-api keepalive - URL:
https://chainshield-api.onrender.com/health - Schedule: Every 14 minutes (custom expression:
*/14 * * * *) - Notifications: email on failure (optional)
- Title:
- Save.
Why 14 minutes specifically: it's just below Render's 15-min idle threshold so the service never sleeps, and Render's free tier gives 750 instance-hours/month which is enough for a single always-on Web Service (24h × 31d = 744h). More frequent pings don't help — once the container is up it stays up until the next idle window.
UptimeRobot is the equivalent fallback if you prefer their dashboard. Free tier: 50 monitors, 5-min minimum interval. Either works.
Render explicitly permits health-check pings from monitoring services. There's no TOS clause forbidding synthetic uptime monitoring — multiple Render-published guides describe this exact pattern.
# 1. health
curl https://chainshield-api.onrender.com/health
# {"status":"ok"}
# 2. create a real anchored policy
curl -s -X POST https://chainshield-api.onrender.com/policies \
-H 'Content-Type: application/json' \
-d '{
"owner": "0x1111111111111111111111111111111111111111",
"rules": {
"maxTransferEth": 1,
"allowedDestinations": ["0x2222222222222222222222222222222222222222"],
"forbiddenSelectors": ["0x095ea7b3"]
}
}' | python3 -m json.tool
# expect an `anchor` block in the response with rootHash + txHashOpen https://chainshield.pages.dev in a browser and run through the four canonical scenes. Click any anchor pill — it opens chainscan-galileo.0g.ai with the storage tx.
| Tier | Cost | Adds |
|---|---|---|
| Render Starter | $7/mo | No sleep, 0.5 vCPU, dedicated workers |
| Render Standard | $25/mo | 2 GB RAM, 1 vCPU |
| Cloudflare Pages | (still free) | No upgrade needed unless you hit 500 builds/mo |
Cloudflare Pages stays free indefinitely for this project's traffic profile. The natural first paid step is Render Starter for $7/mo when you want the cold-start gone.
- First request after 15 min idle is slow. That's the Render free-tier sleep. Pre-warm with
curl https://chainshield-api.onrender.com/health60 s before judging starts. Or rely on the UptimeRobot pinger. anchorfield missing on a fresh policy create. The Render container booted but the wallet isn't funded. Top up at https://faucet.0g.ai or https://cloud.google.com/application/web3/faucet/0g/galileo, then redeploy or wait for the next request — the in-memory cache holds prior policies.- CORS error in the browser console. The Pages URL doesn't match
WEB_ORIGINon the API. Check the regex inrender.yamlmatches your Pages project's domain pattern, redeploy. - Dockerfile build fails with
frozen-lockfilemismatch. The committedbun.lockis out of sync withpackage.json. Runbun installlocally, commit the regenerated lockfile, push. - Cloudflare Pages build fails on
bunx --bun. Cloudflare's runner detects Bun frombun.lock, but very old Pages projects sometimes default to Node. Re-create the project from scratch — preserves the URL and forces Bun detection.
- Custom domain (CNAME
*.chainshield.pages.devto your domain via Cloudflare DNS — free, 5 min) - HTTPS for the API on a custom domain (Render Pro tier $7/mo unlocks this; on free tier the
*.onrender.comsubdomain is the only option) - Static analysis / RUM / metrics dashboards (out of scope for a hackathon)
- Multi-region failover (free tier is single-region only)
For a hackathon submission and a demo URL judges can click for the next 30 days, the setup above is enough.