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.json at 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.json or yarn.lock committed to the repository. The deploy script uses npm ci for reproducible installs; without a lockfile, the install step falls back to npm install and emits a warning in the deploy log.
  • A start script in package.json that runs the production process — for example "start": "node server.js". The deploy script invokes npm start from 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.1 with 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.