Code</>sensei deploys Node.js projects as systemd-managed processes, the same way it handles Django. The differences are in the build step, the package manager, and the entrypoint.
Repository assumptions
- A
package.jsonat the path you put in the requirements file path field of the project form (yes — the field is shared with Django projects; the path it points at decides which technology fires). - A
package-lock.jsonoryarn.lockcommitted to the repository. The deploy script usesnpm cifor reproducible installs; without a lockfile, the install step falls back tonpm installand emits a warning in the deploy log. - A
startscript inpackage.jsonthat runs the production process — for example"start": "node server.js". The deploy script invokesnpm startfrom the systemd unit.
Build step
If your package.json defines a build
script, the deploy runs npm run build after the install
and before the systemd unit is written. This is the right hook for
TypeScript transpilation, webpack/vite bundles, Next.js builds, etc.
The build runs in the release directory, so any artifacts it
produces under ./dist/, ./.next/, or
./build/ are included in the running release. You do not
need to commit build artifacts.
The systemd unit
[Unit]
Description=<project-name> (codesensei)
After=network-online.target
[Service]
User=deploy
Group=deploy
WorkingDirectory=/srv/codesensei/<slug>/current
EnvironmentFile=/srv/codesensei/<slug>/shared/.env
ExecStart=/usr/bin/npm start
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
The platform currently picks the Node version installed at the
distribution level (nodejs apt package or
node dnf package). To pin a specific major version, either
ship an .nvmrc alongside package.json — the
deploy script installs the requested version via NodeSource if the
distro's default does not match — or pre-install nvm on the server and
add the right shims to /etc/profile.d.
Frontend frameworks specifics
Next.js: npm run build produces the
.next/ directory; npm start runs
next start which binds to $PORT. The deploy
sets PORT to a per-project Unix socket via the
HOST/PORT trick — set HOSTNAME
to 0.0.0.0 in your Environment vars if you need a TCP
port instead.
Astro / Vite static builds: If your project
produces a static dist/ and you do not need a Node server
at runtime, switch the project's technology to Static Site in
the project form. The deploy then skips the systemd unit step and the
edge proxy serves the dist/ directory directly. Build
time is the same; runtime memory footprint drops to zero.
NestJS: No special handling. The default
npm run build + npm run start:prod recipe
works without changes.
Common Node-side issues during first deploy
- "npm ERR! enoent ENOENT: no such file … package-lock.json"
— commit your lockfile, or change the deploy to allow
npm install; the lockfile path is the safer default. - Process starts then exits immediately — check that
your start script binds to the host/port the edge proxy expects. Most
issues here are the app binding to
127.0.0.1with no port explicitly set, then exiting because nothing is listening. - "npm WARN deprecated" swarms in the deploy log — informational, not a failure. The deploy still completes.
The socket-vs-port contract
By default the deploy script binds your Node process to a per-project
Unix socket at
/srv/codesensei/<slug>/shared/app.sock. The edge
proxy forwards requests onto that socket, which avoids opening any TCP
ports beyond 80/443 and protects the application from being addressed
directly from outside the server. Most Node frameworks accept a Unix
socket path through the same mechanism as a TCP port; Express,
Fastify, Next.js's next start, and NestJS all do. If your
framework cannot bind to a Unix socket, set
CODESENSEI_TCP_PORT on the project's environment-variable
form to a port between 9000 and 9999, and the deploy script will
configure the edge proxy to forward to that loopback port instead.
Process supervision and zero-downtime restarts
systemd is the supervisor. On a deploy, the script writes the new
release directory, runs the build step, swaps the
current symlink atomically, and then runs
systemctl reload-or-restart <slug>.service. The
distinction matters: if your application implements graceful reload
(SIGHUP-handling), the reload preserves in-flight requests and there is
no perceived downtime. If it does not, the restart kills the old
process and starts a new one — a fast operation but not strictly
zero-downtime. Frameworks that ship with built-in graceful shutdown
(NestJS, Fastify) handle this transparently; for Express applications,
add the standard
process.on("SIGHUP", () => server.close()) pattern to
your entrypoint.
Environment-specific dependency layout
If your package.json separates
dependencies from devDependencies, the
deploy script runs npm ci --omit=dev by default. This
keeps the runtime node_modules lean. If your build step
needs devDependencies at install time (for example, a
TypeScript transpilation step needs the
typescript package), set
CODESENSEI_NPM_INCLUDE_DEV=1 on the project's
environment-variable form. The build runs with dev dependencies in
place, and the deploy script then prunes them with
npm prune --production before the final systemd restart.
The result: a build that has access to its build-time tooling and a
runtime that ships without the developer-only weight.
Static-site deploys
A growing share of Node.js projects produce a fully-static output —
a dist/ directory full of HTML, CSS, JS, and image assets
with no runtime server needed. Astro static builds, Vite SPAs,
Eleventy sites, plain HTML, and the "export" mode of Next.js all fall
into this category. For these projects, switch the
Technology field on the project form from Node.js
to Static Site.
With Static Site selected, the deploy script runs your
build step, then skips the systemd unit step and points
the edge proxy directly at the built output directory. The default
output is ./dist/; override it with
CODESENSEI_STATIC_OUTPUT=./build/ on the
environment-variable form if your build tool produces a different
path. The edge proxy serves the static files with a one-year
Cache-Control on hashed asset paths and a short cache on
HTML files. Memory footprint on the server drops to zero — the deploy
is just files on disk and a proxy rule pointing at them.