benzuko

Deploying BENZUKO to cPanel (Passenger / Node.js Selector)

BENZUKO is a Next.js 15 app with server-side API routes and dynamic response headers, so it must run as a live Node.js process on cPanel — via Phusion Passenger. It is not a static-export site, so you cannot just drop HTML into public_html.

This guide covers three paths: Git deployment, ZIP upload, and rollback — plus the caveats that actually bite people on shared hosting.


⚠️ Read this first — is your plan capable?

Requirement Why If missing
Node.js Selector (Setup Node.js App) Registers the app with Passenger Ask host to enable, or use a VPS + PM2 (ecosystem.config.js)
Node 18.18+ (20 recommended) Next.js 15 / React 19 requirement Pick 20 in the app UI
SSH access Run npm ci / npm run build reliably Use the UI “Run NPM Install” + build in CI instead
~1 GB build memory & outbound network at build time next build + Google Fonts fetch (next/font/google) Build in GitHub Actions or locally, upload the .next output

The single most common failure is next build being killed on shared hosting (out of memory) or failing to fetch Google Fonts (no outbound network). If either happens, use the build-elsewhere strategy below.


Build strategy: on-server vs. build-elsewhere

A. Build on the server (VPS or generous shared plan): run npm ci then npm run build in the app root over SSH.

B. Build elsewhere, upload artifacts (recommended for shared hosting): build in CI or locally, then upload the repo including .next/ and install production deps on the server. The included .github/workflows/release-artifact.yml produces a ready-to-upload benzuko-cpanel.zip on every tagged release.

Either way the app runs the custom server.js (Passenger entry point) against the normal .next build. Standalone output is Docker-only and stays off here (see next.config.js).


Path 1 — Git deployment (GitHub → cPanel)

  1. cPanel → Git™ Version Control → Create.
    • Clone URL: your GitHub repo (use a deploy key / PAT for private repos).
    • Repository Path: /home/CPANELUSER/benzuko — remember this exact path.
  2. cPanel → Setup Node.js App → Create Application.
    • Node version: 20
    • Application root: benzuko (the SAME folder as the repo path above)
    • Application startup file: server.js
    • Add environment variables (see Environment).
  3. Click Run NPM Install, then open the venv terminal and run npm run build (or rely on .cpanel.yml, which does both).
  4. Edit .cpanel.yml: replace CPANELUSER and confirm APP_ROOT. Commit.
  5. Back in Git Version Control → Update from Remote → Deploy HEAD Commit. .cpanel.yml installs deps, builds, and touches tmp/restart.txt to reload Passenger.
  6. Every future release: push to GitHub → Deploy HEAD Commit.

Path 2 — ZIP upload (no Git)

  1. Produce a deployable ZIP:
    • From CI: download benzuko-cpanel.zip from the release-artifact workflow, or
    • Locally: npm ci && npm run build, then zip the project including .next/, server.js, public/, package.json, package-lock.json (exclude node_modules).
  2. cPanel → File Manager → upload the ZIP into /home/CPANELUSER/benzuko and Extract.
  3. Setup Node.js App → Create Application with the same settings as Path 1 (root benzuko, startup server.js, Node 20).
  4. Click Run NPM Install (installs production deps from the lockfile).
    • If you uploaded a prebuilt .next, you’re done. If not, run npm run build in the venv terminal.
  5. Restart the app from the Node.js App UI.

If you ever deploy without the Node.js App UI, use deploy/htaccess.passenger.template to write the .htaccess by hand. When the UI manages the app, leave its generated .htaccess alone.


Restarting & the tmp/restart.txt trick

Passenger reloads the app whenever tmp/restart.txt changes:

mkdir -p ~/benzuko/tmp && touch ~/benzuko/tmp/restart.txt

The Node.js App UI’s Restart button does the same thing.


Environment

Set these in Setup Node.js App → Environment variables (not a committed .env):

Variable Value
NODE_ENV production
NEXT_PUBLIC_APP_URL https://yourdomain.com (fixes SEO/sitemap/robots)
NEXT_PUBLIC_APP_NAME BENZUKO
AUTH_SECRET long random string
RATE_LIMIT_WINDOW_MS / RATE_LIMIT_MAX tune as needed

Do not set PORT — Passenger assigns its own socket. See .env.example for the full list.


Domain, SSL & permissions


Verify

curl -I  https://yourdomain.com          # 200 + security headers
curl     https://yourdomain.com/api/health   # {"status":"ok",...}

Check ~/benzuko/tmp/passenger.log and the Node.js App UI logs if it 502s.


Rollback


Troubleshooting

Symptom Likely cause Fix
502 / “Incident” page App crashed on boot Read tmp/passenger.log; usually missing build or env var
Build killed / OOM Shared-hosting memory cap Build in CI/locally, upload .next
Build fails fetching fonts No outbound network at build Same — build where network exists
CSS/JS 404s .next/static not uploaded Include the whole .next/ folder
Changes don’t show Passenger cached old process touch tmp/restart.txt
Node version errors Selector on Node < 18.18 Switch app to Node 20