How-to
Node.js applications
Run a Node.js application behind ScalWS: started and supervised by ScalWS under
scalwsd.service, or run by systemd and proxied under scalws-controller.
Which way?
| Server unit | How the application runs | Site file key |
|---|---|---|
scalwsd.service | ScalWS starts the processes, waits until they are ready, restarts them when they exit and balances requests across them. | node |
scalws-controller (default) | You run the application yourself (for example as a systemd unit). ScalWS proxies to it. | proxy |
scalws-controller refuses node runtimes, also node with upstream:, because every
instance would start its own copy of the application
(ADR-0036).
Prerequisites
- ScalWS installed, with
include: sites.d/*.yamlin/etc/scalws/scalws.yaml. - The application deployed outside
/home, for example/srv/app, with its dependencies installed (npm ci) and built if needed. - The application listens on the port or socket given in the
PORTenvironment variable. This line works in every case below:
server.listen(process.env.PORT, process.env.HOST);Under scalwsd.service: ScalWS runs Node.js
The quickest way:
sudo scalwsctl site add app.example.com --root /srv/app --node "node server.js"The command is split on whitespace and run without a shell. The site file it writes, with more settings added:
# /etc/scalws/sites.d/app.example.com.yaml
domain: app.example.com
root: /srv/app
node:
command: [node, server.js]
env: {NODE_ENV: production}
workers: {min: 2, max: 6, autoscale: auto}
health_path: /healthz
https_redirect: truenode.command: the argument vector. The working directory isroot.node.env: the environment is cleared and rebuilt; onlyPATH(system directories),LANG,HOME, the listen variables and these entries are set.node.workers: without it, one process. Withautoscale: autothe optimizer moves the count betweenminandmax.node.health_path: a worker gets requests only once this path answers with a non-5xx status.node.listen:unix(default,PORTis a socket path) ortcp(a free loopback port;PORTis a number andHOSTis set), for frameworks that accept only a port number.
Static assets can bypass Node.js with a route. Route paths are not stripped, so the
route’s root is the directory that contains assets/:
domain: app.example.com
root: /srv/app
node: {command: [node, server.js], env: {NODE_ENV: production}}
routes:
- path: /assets
runtime: {type: static, root: /srv/app/public, precompressed: true}For Next.js, profile: nextjs supplies the runtime
(npm run start, listen: tcp, NODE_ENV=production, 60 s start-up timeout); run
npm run build first:
domain: next.example.com
root: /srv/next
profile: nextjsThe processes run inside the unit’s sandbox, where /srv is read-only. A directory the
application writes to needs a drop-in with ReadWritePaths= (see
quick start).
Under scalws-controller: proxy to your own service
Run the application as a systemd unit that listens on loopback only:
# /etc/systemd/system/app-example-com.service
[Unit]
Description=app.example.com
After=network.target
[Service]
User=app
WorkingDirectory=/srv/app
Environment=NODE_ENV=production PORT=3000 HOST=127.0.0.1
ExecStart=/usr/bin/node server.js
Restart=always
[Install]
WantedBy=multi-user.targetsudo systemctl daemon-reload
sudo systemctl enable --now app-example-com
sudo scalwsctl site add app.example.com --proxy http://127.0.0.1:3000The site file:
# /etc/scalws/sites.d/app.example.com.yaml
domain: app.example.com
proxy: http://127.0.0.1:3000The proxy sends the client’s Host header and tunnels WebSockets by default; see
reverse proxy for timeouts, routes and preserve_host.
Check it works
curl -sI -H 'Host: app.example.com' http://127.0.0.1/
sudo scalwsctl site list
scalwsctl apps # under scalwsd.service: workers ready/targetUnder scalwsd.service the application’s stdout and stderr go to the journal with target
scalws::app:
journalctl -u scalwsd -o cat | jq -c 'select(.target == "scalws::app")'Common problems
scalws-controller cannot run a managed Node.js processfromsite add --node, ormanaged node runtimes are not supported with several instanceson reload: use the proxy setup above, or switch toscalwsd.service(sudo systemctl disable --now scalws-controller && sudo systemctl enable --now scalwsd).- 503 with
Retry-After: 1,scalwsctl appsshows no ready worker: the application does not listen onPORT, crashes at start, orhealth_pathanswers 5xx. A worker that is not ready withinstartup_timeout(30 s) is restarted. nodenot found: the command is looked up in the system directories (/usr/local/bin,/usr/bin, …). For a Node.js installed elsewhere, write the absolute path:command: [/opt/node/bin/node, server.js].- Arguments with spaces: use the list form of
command; the string form is split on whitespace. - 502 under the controller: the service is not running or listens elsewhere; test it
with
curl http://127.0.0.1:3000/.
See also: applications, how managed runtimes are run.