Node.js Server

Run a Vercube application on your own server or container with PM2, systemd or Docker.

On a VPS, a VM or in a container, a Vercube application is a plain Node.js process: build it once and run dist/index.mjs.

vercube build
node dist/index.mjs

The entry calls app.listen() without any guard:

src/index.ts
import { createApp } from '@vercube/core';

const app = await createApp();

await app.listen();

export default { fetch: app.fetch.bind(app) };

app.listen() binds the port unless VERCUBE_LISTEN=0 is set. Tools that import the entry only to reach the app, such as vercube scheduler and the Vite dev worker, set it themselves. On Vercel, AWS Lambda and Azure Functions listening is turned off too, so the same entry can be imported there: Vercel drives it through the exported fetch (Vercel), while Lambda and Azure still need their adapters (AWS Lambda, Azure Functions). VERCUBE_LISTEN=1 forces the server up anywhere.

Do not guard app.listen() with if (import.meta.main). Process managers such as PM2 import the entry through a wrapper, so import.meta.main is false there and the server never starts, without an error. On a Node.js release without import.meta.main the guard is undefined and the server never starts either.

PM2

ecosystem.config.cjs
module.exports = {
  apps: [
    {
      name: 'api',
      script: 'dist/index.mjs',
      instances: 'max',
      exec_mode: 'cluster',
      env: { NODE_ENV: 'production' },
    },
  ],
};
pm2 start ecosystem.config.cjs
pm2 logs api

systemd

/etc/systemd/system/api.service
[Unit]
Description=Vercube API
After=network.target

[Service]
WorkingDirectory=/srv/api
ExecStart=/usr/bin/node dist/index.mjs
Environment=NODE_ENV=production
Restart=on-failure
User=www-data

[Install]
WantedBy=multi-user.target
systemctl enable --now api
journalctl -u api -f

Set shutdown.handleSignals in your config so systemctl stop and pm2 reload let requests in flight finish, see Graceful Shutdown.

Docker

Dockerfile
FROM node:24-slim AS build
WORKDIR /app
RUN corepack enable
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
COPY . .
RUN pnpm build

FROM node:24-slim
WORKDIR /app
ENV NODE_ENV=production
COPY --from=build /app/package.json ./
COPY --from=build /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist
EXPOSE 3000
CMD ["node", "dist/index.mjs"]

Bind to all interfaces inside a container with server: { host: '0.0.0.0' }, otherwise the port is only reachable from the container itself.