Deploying Node.js on Vessl
Vessl detects Node.js automatically via package.json and builds your app with Nixpacks. This guide covers Express, Fastify, and other Node.js servers, as well as database connections, background workers, and TypeScript setups.
Prerequisites
- A server added and provisioned in Vessl
- A GitHub repository with your Node.js app
package.jsonat the repository root (or in your Root Directory for monorepos)
What Vessl auto-detects
When Vessl finds package.json it sets:
| Setting | Auto-filled value |
|---|---|
| Build engine | Nixpacks |
| Port | 3000 |
| Post-deploy command | npx prisma migrate deploy (if prisma/ folder detected) or npm run db:migrate (if drizzle/ folder detected) |
Nixpacks reads your package.json scripts:
- Install:
npm ci(oryarn install/pnpm installdepending on lockfile) - Build: runs
npm run buildif abuildscript exists - Start: runs
npm run start
Step 1 — Make sure your app listens on PORT
Vessl injects a PORT environment variable. Your server must read it — hardcoding a port number will cause the health check to time out.
// Express example
const express = require('express');
const app = express();
const port = process.env.PORT || 3000;
app.listen(port, '0.0.0.0', () => {
console.log(`Server running on port ${port}`);
});
// Fastify / TypeScript example
const port = parseInt(process.env.PORT ?? '3000', 10);
await app.listen({ port, host: '0.0.0.0' });
0.0.0.0, not localhostBy default, Node.js servers bind to 127.0.0.1. Inside a container, this means the health check from outside the container can't reach it. Always bind to 0.0.0.0.
Step 2 — Create the app
- In your project, click New Application.
- Select your Node.js repository and branch.
- Vessl detects Node.js — the port defaults to
3000. - If you have a TypeScript build step, verify the build command is set to
npm run build. - Click Deploy.
Step 3 — TypeScript setup
Nixpacks compiles TypeScript projects automatically if it detects a tsconfig.json. Make sure your package.json has:
{
"scripts": {
"build": "tsc",
"start": "node dist/index.js"
}
}
If you use ts-node or tsx directly in production (no compile step), your start command would be:
npx tsx src/index.ts
Set this in the Start command field in the wizard or app Settings.
Step 4 — Add a database
From the Add-ons tab, add PostgreSQL, MySQL, or Redis. Vessl injects connection variables automatically:
PostgreSQL / MySQL:
DATABASE_URL=postgresql://<user>:<pass>@<host>:5432/<db>
DB_HOST=<internal hostname>
DB_PORT=5432
DB_DATABASE=<db name>
DB_USERNAME=<username>
DB_PASSWORD=<password>
Redis:
REDIS_URL=redis://:<password>@<host>:6379
REDIS_HOST=<internal hostname>
REDIS_PORT=6379
REDIS_PASSWORD=<password>
Prisma
If your repo has a prisma/ directory, Vessl sets the post-deploy command to npx prisma migrate deploy automatically. Make sure DATABASE_URL is set (it is, via the add-on) and your prisma/schema.prisma uses the env("DATABASE_URL") datasource.
Drizzle
If your repo has a drizzle/ directory, Vessl sets npm run db:migrate. Add a db:migrate script to your package.json:
{
"scripts": {
"db:migrate": "drizzle-kit migrate"
}
}
Step 5 — Background workers
For queue processors, scheduled jobs, or WebSocket servers, add a daemon from the Daemons tab:
| Use case | Command |
|---|---|
| BullMQ worker | node dist/workers/queue.js |
| cron loop | /bin/sh -c "while true; do node scripts/cron.js; sleep 60; done" |
| WebSocket server | node dist/ws-server.js |
Each daemon runs in its own container with the same environment variables as your web app.
Step 6 — Node version
Nixpacks picks the Node.js version from (in order):
.node-versionfile.nvmrcfileengines.nodefield inpackage.json- Latest LTS if none of the above
To pin a specific version, add to package.json:
{
"engines": {
"node": "20.x"
}
}
Or create a .node-version file:
20
Common issues
Health check times out after a successful build
The most common cause is the server not listening on 0.0.0.0. Check that your app.listen call uses 0.0.0.0 as the host and reads process.env.PORT.
npm run start exits immediately
If your start script is something like node dist/index.js and the dist/ folder doesn't exist, your npm run build step didn't run or failed silently. Check the build logs in the deployment drawer.
TypeScript compilation errors in CI
The Nixpacks build runs tsc with your tsconfig.json settings. If you use "strict": true, errors that TypeScript tolerates in development (any types, missing types) will fail the build. Fix the errors or add "noEmitOnError": false to tsconfig.json if you want to deploy despite type errors.
Out-of-memory during build
Large TypeScript projects can run out of memory during tsc. Set:
NODE_OPTIONS=--max-old-space-size=4096
in the Variables tab before deploying.