Docs
Get Started

Deploy Node.js

This guide takes a Node.js service that runs on your machine and gets it running on Kuberns, with a real start script, a managed database, and a process that shuts down cleanly on redeploy.

Most of the work is in your repository rather than in the dashboard. The AI agent works out the runtime, the dependency file, the port, and the start command on its own. What it cannot do is make a nodemon development setup behave correctly inside a container. That part is below.

Before you start

  • A Node.js service in a GitHub repository that runs locally.
  • Your production entry point. This guide assumes server.js.
  • A Kuberns account. The free trial is enough to follow along.

Prepare your Node.js project

1. Commit the lockfile

package-lock.json, yarn.lock, or pnpm-lock.yaml must be committed. Without one, the build resolves fresh versions of every transitive dependency, and a build that worked yesterday can fail today because a package you have never heard of published a minor release.

This is the cheapest reliability fix available and it is a single commit.

2. Separate the dev script from the start script

A typical development package.json runs nodemon, which watches files and restarts on change. That is wrong in production: it adds a file watcher to a container whose files never change, and it is not a supervisor.

Keep them separate:

{
  "scripts": {
    "dev": "nodemon server.js",
    "build": "tsc",
    "start": "node server.js"
  },
  "engines": {
    "node": ">=20"
  }
}

start must run the built, production entry point. If you use TypeScript, start runs the compiled output — node dist/server.js, not ts-node.

The engines field tells the agent which Node major version your code expects.

3. Listen on the port the platform provides

This is the single most common Node deployment failure, and it produces a successful build followed by an unreachable site:

const port = process.env.PORT || 3000;
 
app.listen(port, "0.0.0.0", () => {
  console.log(`listening on ${port}`);
});

Two things matter. The port must come from process.env.PORT, not a constant. And the host must be 0.0.0.0, not localhost or 127.0.0.1 — a process bound to localhost inside a container accepts connections from nothing but itself.

4. Shut down cleanly

When you redeploy, the platform stops the old process. If it exits immediately, in-flight requests are dropped and open database connections are left for the server to time out.

Handle SIGTERM:

const server = app.listen(port, "0.0.0.0");
 
process.on("SIGTERM", () => {
  server.close(() => {
    // close database pools and queue connections here
    process.exit(0);
  });
});

This is a few lines that most tutorials skip, and it is the difference between a deploy your users notice and one they do not.

5. Declare your processes

Add a Procfile at the repository root:

web: npm start

The agent reads this and turns the web entry into your server resource. Add a line per additional process if you run workers:

web: npm start
worker: node worker.js

Commit and push before continuing.

Deploy

Connect the repository

Create the service from your Git provider and select the branch you want to deploy. The agent then analyzes the repository and proposes a configuration.

The Kuberns AI agent analyzing a repository: live detection log on the left, and the Setup, Analyze Repository, Configure Env, Build, and Deploy phase stepper on the right

Check three fields on the review screen: the root directory if this is a monorepo, the port, and the start command, which should match the Procfile you just committed.

Set the build and start commands

Open deployment configuration and confirm the two phases:

# Pre-build: install dependencies reproducibly
npm ci
 
# Post-build: compile, if your project has a build step
npm run build

npm ci installs exactly what the lockfile specifies and fails if package.json and the lockfile disagree. Prefer it to npm install in a build.

Skip the build command entirely if your project runs plain JavaScript with no compile step.

Set environment variables

Open Environment Variables and add what your service reads:

NODE_ENV     = production
DATABASE_URL = postgres://<user>:<password>@<hostname>/<database>

NODE_ENV=production is not decorative. Express and many libraries use it to switch on caching and switch off verbose error output, and leaving it unset costs real performance.

Environment variables apply on the next deploy, and saving them triggers one automatically.

Add a database

From the environment's Resources tab, add a PostgreSQL, MySQL, or MongoDB datastore.

The environment's Resources tab listing a SERVER, a BACKGROUND WORKER running a celery-start command, a POSTGRES database, and a redis queue, each with its plan, memory, and storage

Its overview page shows the database name, username, password, and hostname. Build DATABASE_URL from those values.

If your project uses a migration tool — Prisma, Knex, TypeORM, Sequelize — add its migrate command as a post-build command so schema changes ship with the code that needs them:

npx prisma migrate deploy

Post-build commands run after every build, which is what you want: a migrate command does nothing when there is nothing to apply.

Confirm it is running

Watch Logs for a clean start.

The environment's Logs tab streaming logs for the web process, each line showing a timestamp, level, source, and message

Your own listening on ... line should appear. If the build succeeded but no such line exists, the process exited — read upward for the error.

After the first deploy

Add your own domain

Add the hostname under Custom Domains and point DNS at the target shown. See Add a domain. SSL is provisioned once DNS verifies.

If your service sets cookies or enforces CORS, update its allowed-origin list to include the new domain.

Deploy from a monorepo

Set the root directory to the package containing the deployable service. Everything else — dependency file, build command, port — is then resolved relative to that directory.

If several packages in one repository are deployable, create a separate Kuberns service for each, all inside the same project, each with its own root directory.

Add a worker

A queue consumer, a scheduler, or any process that is not the web server needs its own background worker resource with its own Procfile command. A process declared in the Procfile with no matching resource simply does not run, and nothing reports an error when it does not.

Common problems

The build succeeds but the site is unreachable. The server is listening on a hard-coded port or on localhost. It must use process.env.PORT and bind 0.0.0.0.

Cannot find module. A package is in devDependencies but needed at runtime, or the build output is not where start expects it. Check that main and the start script agree with your build output path.

The build fails after working locally. No lockfile, or npm install was used instead of npm ci. Commit the lockfile and use npm ci.

Requests fail during a deploy. The old process is being killed with in-flight requests. Add the SIGTERM handler from step 4.

ts-node in the logs. The start script is running TypeScript directly instead of the compiled output. Build in post-build and start from dist/.

The wrong package deployed from a monorepo. The root directory is not set, or points at the repository root rather than the service.