Skip to content

How to deploy an Express / Node.js API on Ownkube

Deploy an Express or plain Node.js API on Ownkube with npm start, a PORT env var, an optional managed Postgres, a health check, and autoscaling.

Abhishek Jaiswal

6 min

On this page 11 sections

An Express API needs almost nothing from a host beyond a process that runs npm start, a port it can bind to, and somewhere to send traffic once it is healthy. Ownkube handles all of that from a git push, with a managed Postgres available if the API needs one.

This walks through deploying a Node.js Express API on Ownkube Compute: the start command, the PORT environment variable, an optional database, a health check endpoint, and horizontal autoscaling.

TL;DR

  • Ownkube runs npm start (or your Dockerfile’s CMD) and expects the app to bind to process.env.PORT.
  • Add a managed Postgres if the API needs a database, and pass its connection string in as DATABASE_URL.
  • Add a /health route so Ownkube’s health checks can tell a live process from a stuck one.
  • Turn on horizontal autoscaling so the API adds instances under load and idles back down when quiet.
  • Metered billing suits most APIs: a mostly-idle service between requests barely registers on the CPU meter.

1. Confirm the app binds to the right port

Ownkube injects a PORT environment variable into the container. The app has to read it rather than hardcoding a port:

server.js
const express = require("express");
const app = express();
app.get("/health", (req, res) => {
res.status(200).json({ status: "ok" });
});
app.get("/", (req, res) => {
res.json({ message: "hello from Ownkube" });
});
const port = process.env.PORT || 3000;
app.listen(port, () => {
console.log(`listening on ${port}`);
});

Make sure package.json has a start script that Ownkube can run directly:

{
"scripts": {
"start": "node server.js"
}
}

You can deploy straight from source, but a Dockerfile gives you a pinned Node version and a smaller runtime image:

FROM node:20-slim AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
FROM node:20-slim
WORKDIR /app
COPY --from=build /app/node_modules ./node_modules
COPY . .
ENV NODE_ENV=production
EXPOSE 3000
CMD ["npm", "start"]

3. Push and create the app

Connect the repo from the dashboard to deploy on every push, or deploy the current directory with okctl:

Terminal window
okctl login
okctl up --name express-api --port 3000

Without a Dockerfile, Ownkube can build straight from package.json and run npm start, which is enough for a simple API with no native dependencies.

Ownkube allocates a *.ownkube.app hostname with automatic TLS, so the API is reachable over HTTPS the moment it deploys.

4. Set environment variables

Set any config the API needs, including secrets, from the dashboard or the CLI. These are injected into the running container and never committed to the repo:

Terminal window
okctl vars set express-api NODE_ENV=production
okctl vars set express-api API_KEY="your-secret-key" --secret

5. Add a managed Postgres, if the API needs one

If your API needs a database, create a Postgres box from New > Database > Postgres in the dashboard. It is a private (in-region) box starting at $4 a month plus storage at $0.25 per GB, with backups and point-in-time recovery available on Personal and Team.

The database runs as a single instance by default; opt-in public access and, on Team, high availability are per-database options. Link it to the API, and Ownkube writes its connection string into the app as a secret DATABASE_URL variable and redeploys it:

Terminal window
okctl deploy link express-api <database-id>

With something like pg or an ORM such as Prisma or Drizzle, point the client straight at process.env.DATABASE_URL:

const { Pool } = require("pg");
const pool = new Pool({ connectionString: process.env.DATABASE_URL });

6. Wire up the health check

Ownkube uses a health check endpoint to know when a new deploy is actually ready to take traffic, and to detect a process that is alive but stuck. Health checks live on the app’s Settings tab, and the path defaults to /health, which matches the route from step 1. Change the path there if your route differs.

A deploy only completes the rollout, and an old instance only gets terminated, once the new instance’s health check passes. That is what keeps rollouts zero-downtime.

7. Turn on autoscaling

Most APIs see uneven traffic through the day. Horizontal autoscaling adds instances when CPU crosses a target and scales back down when the API is quiet. On Ownkube Compute you turn it on through your coding agent (the update_deployment MCP tool) or the API, with a minimum and maximum replica count and a CPU target such as 70%.

Combined with metered billing, an API that is busy for an hour a day and idle the rest of the time pays close to nothing on CPU during the idle hours, since metered pricing bills CPU and memory on what is actually used. Cap each instance on the sliders under Settings → Resources, for example a 500m CPU limit and 512 MiB of memory.

8. Check logs and metrics

Logs and per-instance metrics are in the Ownkube dashboard as soon as the app is deployed, which is usually where a bad health check or a crash loop shows up first, before a user reports it.

Moving to your own AWS later

If the API needs to run inside a specific AWS account, either your own or a customer’s, for compliance or contractual reasons, the same Dockerfile moves over with no rewrite. Ownkube provisions the instance in that AWS account and keeps the same deploy flow, logs, metrics, and health checks attached.

Railway only runs on its own infrastructure. Our Railway alternatives guide compares Railway with the other options.

FAQ

Does Ownkube support WebSockets?

Yes. A standard Express app using ws or socket.io on the same HTTP server works without extra configuration, since Ownkube proxies the app’s single port.

How do I run database migrations for a Node API?

Run them as a one-off job. Create a Job deployment from the same repository with a command such as npx prisma migrate deploy or npx knex migrate:latest, leave the schedule empty, and link the same database to it. Then trigger a run when a release includes new migrations:

Terminal window
okctl deploy link express-api-migrate <database-id>
okctl deploy job-runs trigger express-api-migrate

Can I run a background worker alongside the API?

Yes. Deploy the worker as a separate Ownkube app that shares the same DATABASE_URL, and optionally a Valkey cache box (from $2 a month) if the worker needs a queue.

What happens if I forget to set PORT?

You do not need to set PORT yourself. Ownkube injects it into the container, and the app should read process.env.PORT rather than hardcoding a value, as shown in step 1.

Is there a free tier for small APIs?

No. Compute has no free tier, and you don’t need a subscription either: top up from $5 and pay per minute. The Personal plan is $5 a month and loads $5 of wallet credit you then spend, so the plan price is also usable balance. Either way, unused credit rolls over and never expires.

For the always-current version of this walkthrough, see the Deploy Express guide in the docs.

Where Ownkube fits

An Express API mostly needs a place to run npm start, a port to bind, and a health check that tells the platform when it is actually ready, and Ownkube covers that without a cloud account or a YAML pipeline to write first. Add a managed Postgres if the API needs state, turn on autoscaling if traffic is uneven, and the rollout stays zero-downtime by construction.

See Ownkube pricing for the per-minute wallet model, from $5 a month. Deploy my first app.

Abhishek Jaiswal · Founder and Engineer

Abhishek Jaiswal is the founder and engineer behind Ownkube, a cloud for personal software. He builds the platform and writes here about cloud cost, migrations, and running production infrastructure in your own AWS account without a DevOps team.

Share

More posts

All posts

Ship my first app in the next five minutes.

Push a repo and get a live URL, with no cloud account or config.

Top up from $5, billed per minute with no subscription.