kvbox.sh
Deploy

Node API

Deploy a REST API or any Node server (Express, Fastify, Koa, NestJS, Hono): installed, built and run as a service on port 3000.

A REST API on Express, Fastify, Koa, NestJS or Hono (with @hono/node-server) is detected from package.json. Anything else runs with --template node.

cd my-api
kvbox deploy
# ✓ my-api is live at https://my-api.kvbox.app

The one rule

Listen on process.env.PORT, which is 3000:

import express from "express";

const app = express();
app.get("/health", (req, res) => res.json({ ok: true }));
app.listen(process.env.PORT ?? 3000);

The first kvbox deploy asks for the port (default 3000); a server on another port answers it, or says so with kvbox deploy --port 8080. It is saved to kvbox.json.

What happens

  1. The source is shipped, without node_modules and .git.
  2. The box installs it with your package manager (npm, pnpm or yarn, from the lockfile). devDependencies are installed too.
  3. It runs your build script if you have one (NestJS, TypeScript), then start. Your dev script is never used.

start is required. A TypeScript server can start with tsx, since devDependencies are installed:

{
  "scripts": {
    "build": "tsc",
    "start": "node dist/server.js"
  }
}

The app runs as the systemd service kvbox-app: restarted if it dies, back after a reboot. The deploy waits up to 30 seconds for it to answer on its port; if it never does (it crashed, or listens on another port), the deploy fails and prints the last lines of its log.

A redeploy installs and builds in a staging directory while the old version keeps serving, then swaps and restarts.

Environment variables

Database URLs, API keys: set them with kvbox env (--secret for the ones nobody should read back) and the server gets them. A database of its own is a Postgres box linked to this one.

In the box

kvbox exec my-api -- journalctl -u kvbox-app -n 100     # the server's log
curl https://my-api.kvbox.app/health

Bigger boxes

The box is xs (1 vCPU, 2 GB of RAM) by default. Node uses one CPU, so on a bigger box add pm2 to your dependencies and make it your start script, to use every CPU and reload without a gap: "start": "pm2-runtime start ecosystem.config.js".

On this page