Direct VPS Frontend Deployment¶
Status: Live production frontend path Last Updated: 2026-04-23
This document defines the app-local deployment path for the Wait Time Canada frontend now running on the shared VPS.
Shared-VPS ownership note:
- Shared host topology, ingress ownership, release-root conventions, and live cross-project inventory are canonical in
/home/jer/repos/vps/platform-ops. - Use
/home/jer/repos/vps/platform-ops/docs/standards/PLAT-009-shared-vps-documentation-boundary.mdfor the documentation boundary. - This repo remains canonical for Wait Time Canada's frontend packaging, deploy, verify, and rollback steps.
- The backend scheduler/runtime path now has its own companion runbook in
docs/operations/direct-vps-backend.md.
Current State¶
As of 2026-04-23:
https://wait-time.cais live on the shared VPS through host Caddy.https://www.wait-time.caredirects to the apex host through the same VPS route.platform-opsinventory now recordswaittime-frontendas a live Docker web service.- Netlify should be treated as rollback-only for the frontend path.
- Read-heavy anonymous API routes now use short-lived in-process response caching on this VPS path to reduce Neon public transfer.
Target Runtime Shape¶
The VPS target mirrors the other direct-VPS web apps:
- Docker container for the Next.js frontend
- Host-managed Caddy for public ingress
- Loopback-only upstream bind:
127.0.0.1:3400 -> container:3000 - Release root:
/srv/apps/waittime-frontend - Env file:
/etc/projects-merge/env/waittime-frontend.env - Public health endpoint:
https://wait-time.ca/api/health - Private health endpoint:
http://127.0.0.1:3400/api/health
Required Env Contract¶
Required:
DATABASE_URLNEXT_PUBLIC_MAPBOX_TOKEN
Recommended:
NEXT_PUBLIC_BASE_URL=https://wait-time.caHEARTBEAT_STALE_THRESHOLD_MINUTES=120to match the hourly GitHub Actions backend cadence
Deployment note:
NEXT_PUBLIC_MAPBOX_TOKENandNEXT_PUBLIC_BASE_URLmust be present at image build time because they are embedded into the Next.js frontend bundle.APP_VERSIONis injected by the deploy script so/api/healthcan expose the deployed revision.
Packaging Files¶
The direct-VPS path uses:
frontend/Dockerfilescripts/deploy-vps-frontend.shscripts/release-vps-frontend.sh
Local Preflight¶
Before preparing a release:
cd /home/jer/repos/vps/waittimecanada/frontend
npm run lint
npm run format:check
npm run type-check
npm run test:unit
npm run build
Runtime Usage Guardrails¶
The live VPS frontend is expected to preserve these low-transfer controls:
SystemStatuspolls/api/healthevery 5 minutes and only while the tab is visible.- Shared anonymous read routes keep short cache headers via
frontend/utils/cache.ts. - The VPS runtime applies short-lived in-process response caching via
frontend/utils/server-cache.tsfor repeated reads to/api/health,/api/status,/api/hospitals,/api/hospitals/[slug]/trends,/api/resources,/api/resources/alerts,/api/resources/system-context,/api/resources/water-advisories,/api/resources/aqhi,/api/data-quality,/api/anomalies,/api/compare,/api/methodology, and the analytics routes. - Geolocation and export routes remain
Cache-Control: no-store.
Deploy On The VPS¶
From the checked-out release on the VPS:
cd /srv/apps/waittime-frontend/current
./scripts/deploy-vps-frontend.sh /etc/projects-merge/env/waittime-frontend.env
The deploy script:
- builds the frontend image from
frontend/ - tags it with the git revision when available
- replaces any existing
waittime-frontendcontainer - runs the container with
--restart unless-stopped - binds it privately at
127.0.0.1:3400:3000 - waits for the private
/api/health,/, and/methodsroutes to return200before exiting - sets
APP_VERSIONfor health visibility
Optional override:
WAITTIME_FRONTEND_STARTUP_TIMEOUT_SECONDScontrols how long the deploy script waits for route readiness before failing (default120)
Stage And Release From A Workstation¶
Defaults:
- app root:
/srv/apps/waittime-frontend - env file:
/etc/projects-merge/env/waittime-frontend.env
Operational note:
scripts/release-vps-frontend.sh --deploystages the release remotely before attempting the deploy step.- If the remote deploy step reports
env file not found: /etc/projects-merge/env/waittime-frontend.env, treat that as a likely permissions problem on/etc/projects-merge/env, not as a failed stage. - In that case, SSH to the VPS and rerun:
cd /srv/apps/waittime-frontend/current
sudo ./scripts/deploy-vps-frontend.sh /etc/projects-merge/env/waittime-frontend.env
Verification¶
After deploy, verify:
curl -fsS http://127.0.0.1:3400/api/health
curl -fsS https://wait-time.ca/api/health
PRODUCTION_BASE_URL=https://wait-time.ca ./scripts/production-smoke.sh
Expected outcome:
- private and public health return HTTP
200 - page and API smoke checks pass for
/,/methods,/data-quality,/analytics,/resources,/api/resources/system-context,/api/resources/water-advisories,/api/status, and aggregate/api/data-quality /api/healthreflects the current backend freshness state in the database, sohealthymay befalseeven when the frontend cutover itself is correct
Suggested Caddy shape after cutover:
www.wait-time.ca {
redir https://wait-time.ca{uri} 308
}
wait-time.ca {
encode zstd gzip
reverse_proxy 127.0.0.1:3400
}
Rollback¶
Rollback is release-based:
- identify the previous release in
/srv/apps/waittime-frontend/releases - repoint
/srv/apps/waittime-frontend/current - rerun
./scripts/deploy-vps-frontend.sh /etc/projects-merge/env/waittime-frontend.env - repeat the health and smoke verification
Current Production Rule¶
Treat this as the live production path. If a rollback is required, document the reason and flip shared status surfaces in platform-ops back to rollback mode instead of treating Netlify as silently active.