DeployRuntimes

Node

A package.json selects the Node runtime and holds the start script that runs your app.

A package.json is required — it both selects the Node runtime and holds the command that starts your app. On deploy, agenthost installs your dependencies, runs your build script if you have one, then starts the app with npm start.

  • Define a start script — it is the entrypoint. Without one, npm start has nothing to run and the app won't come up.
  • A build script runs automatically before start, if present (e.g. to compile TypeScript or bundle a frontend). It's skipped when absent.
  • Dependencies are installed from package.json (using the lockfile when you include one). Don't ship node_modules.
package.json
"scripts": {
  "build": "tsc",              // optional — runs if present
  "start": "node server.js"    // required — how your app starts
}

Supported versions are 20, 22 and 24, defaulting to 22 — see Choosing a version.

Listen on the right port

Traffic is routed to port 3000. Read PORT and fall back to 3000, which is what most frameworks do already:

server.js
const port = process.env.PORT || 3000;
app.listen(port, "0.0.0.0");

Bind to 0.0.0.0, not 127.0.0.1 — a server listening only on localhost is unreachable from outside its container, and presents as a container that starts and then times out.

What gets built

FROM node:<version>-slim AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci || npm install
COPY . .
RUN npm run build --if-present

FROM node:<version>-slim
WORKDIR /app
ENV NODE_ENV=production
COPY --from=build /app ./
EXPOSE 3000
CMD ["sh", "-c", "npm start"]

Two consequences worth knowing:

  • NODE_ENV=production is set at run time, so anything gated on it behaves accordingly. Your build step runs before that, in the build stage, so dev dependencies are available while building.
  • npm ci falls back to npm install when there's no usable lockfile. Ship package.json and package-lock.json together or ship neither — a lockfile out of sync with the manifest fails npm ci and then installs different versions on the fallback.

Common failures

SymptomCause
"Missing script: start"No start script in package.json
Starts, then times outListening on 127.0.0.1, or on a port other than 3000
Build fails on installLockfile out of sync with package.json
Module not found for a package you havenode_modules was shipped instead of the manifest
TypeScript app starts and exitsstart points at a .ts file — point it at the compiled output, and compile in build

Beyond this

Need a different process manager, a worker, or system packages? Include your own Dockerfile at the project root and it's used instead of this one — see the escape hatch.