01-overview-and-architecture.md# 01. Overview and Architecture
# Goal
Provide a standard, repeatable way to host multiple Node.js applications on one Rocky/EL9 VPS with:
- multiple Node versions
- TLS via Nginx
- systemd-managed processes
- release-based deployments
- shared env files
- constrained CI/CD permissions
# Live architecture on this VPS
Internet
-> Nginx :80/:443
-> proxy_pass http://127.0.0.1:<node-port>
-> systemd service node-site@<domain>.service
-> /usr/local/bin/node-site-runner <domain>
-> /usr/local/bin/node-with-fnm --version <18|20|22>
-> npm start
# Deployment architecture
repo (/srv/git/<domain>)
-> deploy-node-release
-> new release under /var/www/nodeXX/<domain>/releases/<timestamp>
-> shared links restored (.env.local, etc.)
-> current symlink switched
-> service restart
-> healthcheck
# Bootstrap architecture
For a brand-new domain, the toolkit can also bootstrap a starter site in one step:
bootstrap-node-site
-> create /srv/git/<domain>
-> write starter package.json/server.js/.node-version/.gitignore
-> generate package-lock.json with the selected Node version
-> git init + initial commit
-> create-node-site
-> init-node-shared-env
-> cicd-deploy-node-site
This is useful when you want a fresh Node site to become reachable immediately, before replacing the starter app with a real repository.
# Release layout
Each Node site follows:
/var/www/node22/example.com/
current -> releases/<timestamp>
releases/
shared/
# Why this is used
- allows atomic-ish deploys
- keeps old releases for rollback inspection
- separates persistent data from code
- matches the PHP hosting approach already used on this VPS
# Shared env design
Both runtime and build/deploy phases now auto-load:
.env.env.local
This means secrets and environment-specific configuration can live in:
/var/www/nodeXX/<domain>/shared/.env.local
and be symlinked into each release.
# Process model
The Node process is not started directly by Nginx and does not rely on a login shell.
Instead:
- systemd starts
node-site-runner node-site-runnerloads/etc/node-sites/<domain>.env- it chooses the correct release directory
- it loads shared env files
- it uses fnm to run the requested Node version
- it starts the app command (default
npm start)
This keeps the runtime deterministic and suitable for reboot persistence.
# Why fnm was chosen
- simple per-user multi-version Node management
- works for build and runtime
- lightweight
- easy to export to another VPS
Installed for the git user at:
/home/git/.local/share/fnm
# Security model
# Runtime users/groups
- app owner:
git - deploy user:
cicd - web-readable group:
webread - Nginx is a member of
webread
# Restricted CI/CD sudo
cicd does not get blanket root.
It is limited to specific deployment commands and service operations described in the CI/CD doc.
# Validation already performed on this VPS
The reference sites have been verified for:
- systemd startup
- fnm Node20 and Node22 runtime
- release switching
- shared env loading
- Nginx proxying
- HTTPS for
node.v4.sohophp.app - HTTP local host-header probing for
node20.v4.sohophp.app - healthchecks
- cicd-triggered deployment
- one-command starter-site bootstrap