RE:NODE

Deploy a Node.js app on Renode

How a RE:NODE Node.js server starts your repository: the start script, the port from SERVER_PORT, Next.js, Vite and TypeScript builds, choosing the Node version, and the errors first deploys hit.

All docs pages

A Node.js server runs your repository from /home/container. The reliable way to start anything on it - Express, Fastify, Next.js, NestJS, a bot - is a start script in package.json that listens on the port in SERVER_PORT.

How it starts#

With a repository linked on the GitHub tab, every start goes like this:

  1. The branch is fetched into /home/container (on every start if "Pull the latest commit every time the server starts" or "Restart the server when someone pushes" is on; otherwise on the first start only).
  2. PORT is set to the server's port (unless your project sets PORT itself), and HOST and HOSTNAME to 0.0.0.0.
  3. If package.json has a start script: npm install, then npm run start.
  4. Without a start script, the first file that exists among index.js, server.js, app.js, main.js, bot.js, index.mjs, index.cjs, then src/index.js, src/server.js, src/app.js, src/main.js, then the .ts versions of those names, is started with node - or ts-node for a .ts file.

Relying on step 4 is fragile. Define start.

npm install installs devDependencies too, so build tools such as typescript can stay there when the start script builds.

A minimal app#

Two files are a complete, deployable app:

package.json
{  "name": "hello-renode",  "private": true,  "engines": { "node": ">=20" },  "scripts": {    "start": "node index.js"  }}
index.js
const http = require("node:http");// The server's port. PORT is set from SERVER_PORT when the panel starts the repo.const port = Number(process.env.PORT || process.env.SERVER_PORT);if (!port) {  console.error("No port: SERVER_PORT is not set");  process.exit(1);}http  .createServer((req, res) => {    res.writeHead(200, { "Content-Type": "text/plain" });    res.end("Hello from Renode\n");  })  .listen(port, "0.0.0.0", () => {    console.log(`Listening on 0.0.0.0:${port}`);  });

With Express the only lines that matter are the same two: read the port from the environment and bind 0.0.0.0.

server.js
const express = require("express");const app = express();app.get("/", (req, res) => res.send("Hello from Renode"));app.listen(Number(process.env.PORT || process.env.SERVER_PORT), "0.0.0.0");

Frameworks that need a build#

There is no separate build server: the build runs inside your server, with its memory and CPU. Put the build in the start script, or build locally and commit the output.

FrameworkA start script that works
Next.jsnext build && next start -H 0.0.0.0 -p $SERVER_PORT
Nuxtnuxt build && node .output/server/index.mjs
Vite (preview server)vite build && vite preview --host 0.0.0.0 --port $SERVER_PORT
Angularng build && npx http-server dist -a 0.0.0.0 -p $SERVER_PORT
TypeScripttsc && node dist/index.js

Never use a development server as start - next dev, vite, nodemon. They are slow, unminified, watch the filesystem, use more memory, and often bind localhost, where nothing outside the container can reach them.

TypeScript#

Compile in the start script, or commit the compiled dist/. Do not depend on a global ts-node.

package.json
{  "scripts": {    "build": "tsc -p tsconfig.json",    "start": "tsc -p tsconfig.json && node dist/index.js"  },  "devDependencies": { "typescript": "^5.6.0" }}

Choosing the Node version#

The Node.js version is the server's runtime image. Pick it under Runtime version on the GitHub tab, or the Docker image on the Startup tab. Declare the version your project expects in engines.node in package.json or in an .nvmrc file - the pre-flight check compares them with the server's runtime and warns when they differ, because packages built for one Node major can fail to install or run on another.

Environment variables#

Upload .env through the Files tab into /home/container, or set variables on the Startup tab. If you use dotenv, load it first:

javascript
require("dotenv").config();

Commit a .env.example with every key and no values, and keep .env in .gitignore.

Checklist#

  • package.json has a start script that starts the production server.
  • The app listens on process.env.PORT or process.env.SERVER_PORT, on 0.0.0.0.
  • Anything that must be built is built by the start script, or the output is committed.
  • package-lock.json is committed; node_modules is not.
  • No password, token or key is committed; .env is in .gitignore.
  • The Node version in engines.node or .nvmrc matches the server's runtime.
  • Files the app writes at run time go under /home/container.

Common errors#

The container starts and immediately exits. There is no start script and none of the entry files exist. Add "start": "node <your main file>".

`npm run start` cannot find `dist/index.js` or `.next`. The build never ran. Build in the start script, or commit the output.

The console says it is listening, but the address does not answer. The app is bound to localhost or to a hardcoded port. Use 0.0.0.0 and the port from the environment.

The console stops mid-build with no error. The build hit the memory limit. See the warning above.

Packages fail to install or crash with a native-module error. The Node version differs from what the project was built with. Match the runtime version to engines.node, and never commit node_modules built on Windows or macOS.

More in Troubleshooting.

Questions this does not answer? Open a ticket from the panel, or start from the overview. These pages are also available as plain text for AI assistants at /llms.txt.