Skip to content
Dashboard
Create 5 min read

Web Services

Run a public API or full-stack application that listens on an HTTP port.

Web Services#

Choose Web Service for public APIs, application backends, and full-stack servers that need to listen on a network port. It starts a persistent process behind a public URL, discovers its TCP listener, and assigns isolated Docker ingress before the release becomes ready.

A Web Service is intentionally general-purpose: Express, Fastify, Koa, NestJS, Django, Flask, FastAPI, Go’s net/http, and similar servers all fit. The process must keep running and bind to 0.0.0.0 so it is reachable from the container network.

When to use — and when not to#

Use Web Service when:

  • The deliverable is an API server, a full-stack server that mixes API routes and UI, or any backend that needs an HTTP port and public hostname.
  • Multiple clients (browser, mobile, third-party webhooks) will call the same service.
  • You want external HTTP ingress and release-gated TCP readiness without running your own reverse proxy.

Do not use Web Service when:

  • The deliverable is a pure server-rendered UI such as Next.js with SSR or a Nuxt server — use Frontend App so the frontend execution model is explicit in the project.
  • The process should not be reachable from the public internet — use Private Service.
  • The work is a long-lived queue consumer (use Background Worker) or a schedule-driven task that finishes and exits (use Cron Job).

Required runtime behavior#

Your application must start successfully and listen on a TCP port bound to 0.0.0.0, not only to localhost. NexHost provides PORT=3000 as a compatibility default and automatically discovers the port the process actually opens. Each service is isolated in its own container, so many services may safely use 3000 internally.

A Node server normally follows this pattern:

ts
const port = Number(process.env.PORT);
if (!Number.isFinite(port)) throw new Error("PORT is required");
app.listen(port, "0.0.0.0", () => {
  console.log(`Listening on ${port}`);
});

The same two lines appear across stacks:

bash
# Python — gunicorn
gunicorn app:app --bind 0.0.0.0:$PORT
# Python — uvicorn (FastAPI)
uvicorn app:app --host 0.0.0.0 --port $PORT
# Go
# listen on :${PORT}

NexHost does not ask for a health path and does not fetch a customer URL during deployment. It waits for the container’s listener to become reachable, then creates the public route. You may keep a /health endpoint for external monitoring, but it is not required to deploy.

Warning

You can use process.env.PORT or a fixed internal port such as 3000; NexHost discovers it. Binding only to 127.0.0.1 is not supported because the runtime must reach the service through the container network.

Configure and deploy#

  1. Select source. Choose a supported source — a repository, ZIP archive, local folder, public Git URL, or an existing container image when the dashboard offers it. Confirm the branch and root directory.
  2. Configure commands. Enter the build and start commands for the project. A Node service typically uses npm ci/npm install for install and npm run build for build; the Start command must keep a process alive (for example npm start).
  3. Set environment variables. Add secrets and configuration with the right scope — runtime values (database URLs, tokens) should be scoped to Runtime or Both. See Environment Variables.
  4. Deploy and verify. NexHost detects the listener and allocates ingress automatically. On success the service gets a public hostname.

Use the build log to diagnose dependency or build failures, and the deployment details to distinguish a successful build from an application startup failure. A clean build followed by a readiness timeout is usually a start-command, binding, or application-initialization problem rather than source.

How readiness reports#

The deployment detail page splits the release into stages for exactly this reason:

  • Source / Dependencies / Build — did we prepare and compile the code?
  • Publish or start — did the output directory exist and did the process launch?
  • TCP readiness — did the process open a listener that NexHost could reach through the container network before the startup deadline?

When a bug report says "the build passed but the URL does not load," the answer is almost always in the startup-readiness stage. Compare one deployment’s start command and binding behavior to the next, changing only one input between attempts.

When to choose Frontend App instead#

Use Frontend App when the service is primarily a server-rendered user interface. Both types are public Node runtimes and both require a start command and a listener bound to 0.0.0.0, but the dedicated Frontend App choice makes the frontend execution model explicit in the project workflow and helps reviewers and on-call engineers reason faster about which service owns the UI.

Troubleshooting#

SymptomCheck
Startup readiness times outThe process must remain alive, reach its server listen call, and bind to 0.0.0.0. Run the same start command locally and confirm the server opens a TCP listener.
Connection refusedBinding to 127.0.0.1 rather than 0.0.0.0 is the most common variant.
Build passes, app instantly exitsThe start command builds rather than runs (npm run build instead of npm start), or initialization is blocked by an unavailable dependency.

See Deployment Troubleshooting and Logs for systematic reading of each stage’s output.