Deploy a Node / Express API

A JSON API on Express, live on an HTTPS URL.

An Express API is the simplest thing to run on Dockhold: it's already a long-running server. You just need it to listen on the assigned port. This recipe is the whole path.

In a hurry? Start from the ready-made express-api-starter template — click Use this template on GitHub, then deploy it. Or follow the steps below to set up a project you already have.

1. Listen on the assigned port

Read the port from the environment and bind all interfaces:

const express = require("express");
const app = express();

app.get("/health", (_req, res) => res.send("ok"));

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

And give it a start script in package.json:

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

Your app must listen on 0.0.0.0 and read its port from the PORT environment variable — never localhost, never a hardcoded port. Dockhold assigns PORT at runtime; an app that ignores it can't receive traffic.

2. Read config and secrets from the environment

Set plain config (feature flags, public URLs) in the dashboard. Put secrets — API keys, tokens, passwords — in the Vault; they're encrypted and injected at runtime, so they never live in your repo. Read everything from process.env; don't hardcode values.

3. Add a database (optional)

Enable the managed Postgres add-on and Dockhold injects DATABASE_URL. Read it directly:

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

Apps are stateless — the container filesystem is wiped on every restart and deploy. Persist anything you need to keep in the database, not on local disk.

4. Connect the repo and deploy

  1. Push the project to a GitHub repository.
  2. Open the dashboard, connect GitHub, and pick the repo.
  3. Dockhold detects the Node project, runs npm install, and starts it with npm run start. No Dockerfile needed.

Your API goes live at https://<your-app>.dockhold.app with HTTPS handled for you. Every later push to your main branch redeploys.

Troubleshooting

  • URL times out: you must pass "0.0.0.0" to app.listen and use process.env.PORT — binding localhost or a fixed port means no traffic reaches you.
  • CORS errors from a browser frontend: set the allowed origin to your deployed frontend URL via an env var; don't hardcode http://localhost.
  • Database connection refused: read process.env.DATABASE_URL — don't point at localhost.

Next