# Web Services

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

Source: https://nexthomelabs.com/docs/services/web-services
Markdown: https://nexthomelabs.com/docs-md/services/web-services
Slug: services/web-services
Section: Create
Last updated: 2026-08-30
Reading time: 5 min read

---

# 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](/docs/services/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](/docs/services/private-services).
- The work is a long-lived queue consumer (use [Background Worker](/docs/services/background-workers)) or a schedule-driven task that finishes and exits (use [Cron Job](/docs/services/cron-jobs)).

## 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](/docs/configuration/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](/docs/services/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

| Symptom | Check |
| --- | --- |
| Startup readiness times out | The 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 refused | Binding to `127.0.0.1` rather than `0.0.0.0` is the most common variant. |
| Build passes, app instantly exits | The start command builds rather than runs (`npm run build` instead of `npm start`), or initialization is blocked by an unavailable dependency. |

See [Deployment Troubleshooting](/docs/deployments/troubleshooting) and [Logs](/docs/operations/logs) for systematic reading of each stage’s output.

## Related documentation

- [Frontend Apps](/docs/services/frontend-app) — dedicated UI runtime on the same public-runtime foundation.
- [Private Services](/docs/services/private-services) — when the same runtime behavior belongs only to the workspace network.
- [Environment Variables](/docs/configuration/environment-variables) — scoping so build-time and runtime secrets arrive in the right phase.
- [Domains and Networking](/docs/configuration/networking) — public vs private access models.

