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:
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:
# 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.
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#
- 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.
- Configure commands. Enter the build and start commands for the project. A Node service typically uses
npm ci/npm installfor install andnpm run buildfor build; the Start command must keep a process alive (for examplenpm start). - 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.
- 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#
| 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 and Logs for systematic reading of each stage’s output.
Related documentation#
- Frontend Apps — dedicated UI runtime on the same public-runtime foundation.
- Private Services — when the same runtime behavior belongs only to the workspace network.
- Environment Variables — scoping so build-time and runtime secrets arrive in the right phase.
- Domains and Networking — public vs private access models.