Hostinger Deployment Guide¶
🏗️ How Deployment Actually Works¶
Deployment is automated CI/CD that runs nightly or when you start it — there is
no manual VPS/PM2/nginx setup. Since 2026-10-03, merging into main does not
deploy. Instead:
- Nightly, started by the owner's PC (since 2026-10-14, issue 288): after the
04:30 health check passes vitest AND pytest on GitHub's
main,scripts/health-check-deploy-gate.ps1marks that commit as tested and starts the workflow. The workflow deploysmainonly ifbackend/,frontend/,docs/,mkdocs.ymlor the workflow changed since the commit that is live (read fromdeploy-info.json;scripts/ci/deploy-needed.shdecides), and it skips its own test jobs when the commit it builds carries the PC's mark. A night with nothing new costs one short check job. - Fallback schedule, 13:00 UTC: does the same on a day the PC started nothing (PC off, or a test failed), with the cloud tests.
- By hand, any time: press Run workflow (GitHub → Actions → "Deploy
Frontend to Hostinger", branch
main) or rungh workflow run deploy-hostinger.yml --ref main. A manual run always deploys.
Why (GitHub Free's Actions minutes) is in the workflow file's header comment. The two services live in different places:
- Frontend + docs → built by GitHub Actions and served by Hostinger
(static site) at https://pcbgenerator.com (docs at
/docs/). - FastAPI backend → runs on Render
(
https://ai-assisted-pcb-stackup-generator.onrender.com); the frontend is built to call it viaVITE_API_URL.
There is no Node.js backend, no Ollama, no PM2, and no nginx in the current architecture. AI chat is served by the FastAPI backend over SSE.
The pipeline is defined in
.github/workflows/deploy-hostinger.yml.
🚀 The Deploy Flow¶
nightly (the PC's start, or the 13:00 UTC fallback) if site files changed,
or you press Run workflow on main (merging into main no longer deploys)
│
▼
┌──────────────────────┐ pytest backend/tests/ must pass
│ Job 1: test-backend │ (installs CPU-only PyTorch + backend deps;
└──────────┬───────────┘ skipped when the PC already tested this commit)
│ success or skipped
▼
┌──────────────────────────────────────────────────────────┐
│ Job 2: build-and-deploy │
│ • npm ci + `npx vitest run` (gates the deploy; nightly: │
│ none if only docs changed, only the backend-reading │
│ parity tests if nothing in frontend/ or config/ did; │
│ none if the PC already tested this commit) │
│ • npm run build (VITE_API_URL → Render backend) │
│ • mkdocs build (docs site) │
│ • assemble deploy_out/ (frontend at /, docs at /docs) │
│ • write deploy-info.json │
│ • force-push orphan commit → `hostinger-deploy` branch │
│ • curl RENDER_DEPLOY_HOOK_URL (triggers backend deploy) │
└──────────┬───────────────────────────────────────────────┘
▼
Hostinger pulls `hostinger-deploy` → public_html (its Git auto-deploy)
Render rebuilds the FastAPI backend (deploy hook)
Why the branch hand-off? GitHub-hosted runners are blocked from Hostinger's
FTP (21) and SSH/SFTP (65002) ports, so the runner never connects to Hostinger
directly — it publishes the built tree to the hostinger-deploy branch and
Hostinger's Git integration pulls it into public_html.
✅ Triggering a Deploy¶
- Nightly (automatic): started by the owner's PC after its 04:30 health check
passes both test suites, or by the 13:00 UTC fallback schedule on a day the PC
started nothing; only if
backend/,frontend/,docs/,mkdocs.ymlor the workflow changed since the live commit. Merging intomaindoes not deploy. - Manual: run the Deploy Frontend to Hostinger workflow via
workflow_dispatch(Actions tab → Run workflow). It always deploys.
A night where only unrelated files changed (e.g. .claude/, root scripts)
does not deploy.
🔑 Required GitHub Secrets¶
Set these in the repo's Settings → Secrets and variables → Actions:
| Secret | Used for |
|---|---|
VITE_SUPABASE_URL, VITE_SUPABASE_ANON_KEY |
Frontend auth/DB at build time |
VITE_POSTHOG_KEY, VITE_POSTHOG_HOST, VITE_SENTRY_DSN |
Observability (dormant no-ops when unset) |
RENDER_DEPLOY_HOOK_URL |
Triggers the Render backend deploy |
GITHUB_TOKEN |
Force-push the built site to hostinger-deploy (auto-provided) |
Feature flags are baked into the build by the workflow (AI-native shell,
standalone impedance/link-budget/antenna, planar filter, thermal management,
chat prompts/auto-open all =true). Change them in the Build frontend step
of the workflow, not in a server .env.
🔍 Verifying What's Live¶
The build writes a web-servable marker at the site root:
curl https://pcbgenerator.com/deploy-info.json
# -> {"commit":"<sha>","ref":"main","built":"<UTC>","run_id":"<id>"}
Compare commit against the main HEAD you expect. Then spot-check:
- Frontend: https://pcbgenerator.com
- Docs: https://pcbgenerator.com/docs/
- Backend health:
https://ai-assisted-pcb-stackup-generator.onrender.com/health
🔐 Hostinger Git Integration (one-time)¶
Hostinger pulls the hostinger-deploy branch via its Git auto-deploy feature.
The SSH key/connection setup for that integration is covered separately:
Mailbox/alias setup for the support@pcbgenerator.com address is in
HOSTINGER_EMAIL_SETUP.md.
📥 Feedback Storage¶
User feedback (thumbs up/down, comments) is written by the FastAPI backend as
JSONL, one file per UTC day: backend/data/feedback/feedback_YYYY-MM-DD.jsonl
(each line is one submission: source, trigger, rating, comment,
context, ts, ip). Because the backend runs on Render, this lives on
the Render service's filesystem — attach a Render persistent disk (or export
periodically) if you need it to survive redeploys, since a service's ephemeral
disk is reset on each deploy. The write path is POST /api/feedback; point it
at a DB/Supabase later if durable storage is needed.
🛠️ Troubleshooting¶
- Deploy didn't run — the push didn't touch a watched path, or it landed on
a branch other than
main. Use the manualworkflow_dispatchtrigger. - Deploy ran but site unchanged — check
deploy-info.json'scommit; if it matches, Hostinger may not have pulled yet (its Git integration polls) — give it a minute or trigger a pull from hPanel. - Backend errors after deploy — the frontend deployed but the Render deploy
hook failed or Render is cold-starting; check the Render dashboard and
/health. See ../TROUBLESHOOTING.md. - Tests blocked the deploy —
test-backend(pytest) or the frontendvitest rungate failed; the deploy correctly does not publish. Fix the failing test and re-push.