Deploy a Node.js app on Bahriya

This quickstart deploys a Node.js application — an Express API is the running example — as an HTTP container. It listens on port 3000, builds into a small production image, and serves traffic over HTTPS on its default hostname within a minute or two.

Updated 3 Aug 20262 min read

This quickstart deploys a Node.js application — an Express API is the running example — as an HTTP container. It listens on port 3000, builds into a small production image, and serves traffic over HTTPS on its default hostname within a minute or two.

Use this flow for any Node.js HTTP server: Express, Fastify, Koa, NestJS, or a plain http server. The only requirement is that your process binds to 0.0.0.0 on a fixed port and logs to standard output.

Listen on the right port

Bahriya routes traffic to the port your container declares. Read it from the environment so the same image works locally and in production, and always bind to 0.0.0.0:

const port = process.env.PORT || 3000;
app.listen(port, '0.0.0.0', () => {
  console.log(`listening on ${port}`);
});

Add a small health endpoint that returns 200 when the app is ready. Bahriya uses it to decide when a new deployment can start serving:

app.get('/healthz', (req, res) => res.sendStatus(200));

Dockerfile

A multi-stage build keeps the runtime image small — dependencies are installed in one stage and only the production artefacts are copied into the final image:

# Build stage
FROM node:22-slim AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build --if-present
 
# Runtime stage
FROM node:22-slim
WORKDIR /app
ENV NODE_ENV=production
COPY package*.json ./
RUN npm ci --omit=dev
COPY --from=build /app .
EXPOSE 3000
CMD ["node", "server.js"]

Adjust the CMD to your entry point (for example dist/main.js for a compiled NestJS app).

Deploy from the Console

  1. Open your project and create a new HTTP container.
  2. Set the image to your built image reference (for example ghcr.io/myorg/api:1.0.0), and select a registry if the image is private.
  3. Set the port to 3000 and the health check path to /healthz.
  4. Choose one or more regions, set CPU and memory, and create the container.

Once running, it is reachable at https://<handle>.<region>.x.on.bahriya.app.

Deploy with Reis

reis container:create \
  --type http \
  --name "Web API" \
  --handle web-api \
  --image ghcr.io/myorg/api:1.0.0 \
  --project my-project \
  --port 3000 \
  --healthcheck /healthz \
  --cpu 500 \
  --memory 512 \
  --replicas 2 \
  --max_replicas 8 \
  --regions falkenstein-1 \
  --regions virginia-1 \
  --env NODE_ENV=production

--max_replicas enables autoscaling between --replicas and --max_replicas.

Environment variables, secrets, and domains

  • Pass non-sensitive configuration as environment variables — --env KEY=value in Reis, or the Environment section in the Console. See Environment variables.
  • Keep passwords and API keys in secrets. They are encrypted at rest and injected as env vars at runtime — reference one with --secrets DB_PASSWORD=db-password.
  • Point your own hostname at the container by following Custom domain setup.

Next steps