Python Quickstart
Deploy a Python HTTP application as a NexHost Web Service.
Python Quickstart#
Use a Web Service for a Python HTTP application. NexHost waits for a reachable TCP listener, so the application must start a persistent server bound to 0.0.0.0; the platform discovers the listener and assigns ingress automatically.
This guide uses Flask for the example and notes the matching FastAPI variant. The contract is the same for both: bind to 0.0.0.0 and keep the process alive. A fixed internal port is safe because containers are isolated.
When to use this vs a different service#
Use Web Service (this guide) when:
- The deliverable is an HTTP server that should answer browser, mobile, or webhook callers over a public hostname.
- You need readiness gating: callers should only reach the service once it has proved it can serve requests.
Do not use Web Service when:
- The process should be reachable only from inside the workspace — choose Private Service.
- The process is a persistent asynchronous consumer with no HTTP — choose Background Worker.
- The process should run on a schedule and exit — choose Cron Job.
Before you begin#
- Python 3.10+ locally so you can verify the server starts and opens a listener before deploying.
- A fresh directory for the project, or an existing service directory inside a monorepo.
- A workspace and permission to create a project.
Create a Flask application#
In an empty directory, create app.py:
import os
from flask import Flask
app = Flask(__name__)
@app.get("/")
def home():
return "Hello from NexHost"
@app.get("/health")
def health():
return {"ok": True}
if __name__ == "__main__":
# NexHost supplies PORT=3000 as a compatibility default.
app.run(host="0.0.0.0", port=int(os.environ["PORT"]))Why this shape works on the platform:
os.environ["PORT"]reads NexHost's compatibility default. A fixed internal port is also discoverable.host="0.0.0.0"binds to the container interface, not only to loopback. Binding only to127.0.0.1makes TCP readiness observe "connection refused" even when your direct localhost test passed./healthis optional application monitoring; NexHost deployment readiness is based on TCP reachability.
Create requirements.txt in the same directory:
Flask
gunicornFlask is the framework; gunicorn is the production WSGI server you will actually run. TCP readiness verifies that the running gunicorn workers opened a listener, not just that the file imports correctly.
Test locally before you deploy:
```bash
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
PORT=3000 python app.py &
curl -i http://127.0.0.1:3000/health
# expect 200 with {"ok": true} quickly and without authentication
```
Create a FastAPI variant (optional)#
If you prefer FastAPI, the same readiness contract applies — only the runner changes:
# app.py
import os
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def home():
return {"message": "Hello from NexHost"}
@app.get("/health")
def health():
return {"ok": True}requirements.txt:
fastapi
uvicornThe platform-observed difference is only the start command: Flask uses gunicorn, FastAPI uses uvicorn.
Configure the service#
- Select Web Service in New Project — not Frontend App and not Private Service.
- Connect the repository or archive containing
app.pyandrequirements.txt. Verify the branch and root directory when the dashboard shows them. - Set Build command to
pip install -r requirements.txt. If your project lives in a subdirectory of a monorepo, point the root directory there first so this path resolves. - Set Start command to
gunicorn app:app --bind 0.0.0.0:$PORT. This keeps workers alive and routes them at the injected port. - Deploy. The deployment detail page will show source preparation, dependency install, build (which is this
pip install), launch, and TCP readiness — in that order.
For FastAPI, use a comparable uvicorn start command that sets --host 0.0.0.0 and --port $PORT:
uvicorn app:app --host 0.0.0.0 --port $PORTThe flags look slightly different (--bind vs --host/--port) but the meaning is identical: read the injected PORT, bind to the correct interface, stay alive.
What success looks like#
- The deployment status becomes successful and the detail page shows a populated
PORT-aware log line rather than a hard-coded startup announcement. - The dashboard shows a generated hostname for the service. Opening
https://<hostname>/healthreturns{"ok": true}quickly. Openinghttps://<hostname>/returns the greeting. - A fresh deployment after updating only an environment variable produces the updated
/healthresponse with the new value inlined to the configuration — no code edit was required.
Troubleshooting#
| Symptom | Check |
|---|---|
| Startup readiness times out | Is gunicorn/uvicorn the start command rather than python app.py without gunicorn? Does the server reach its listen call after initialization? |
| Connection refused | The container bound only to 127.0.0.1. Bind to 0.0.0.0. |
| "Module not found: app" | The service’s root directory does not contain app.py, or requirements.txt referenced the wrong package. |
| Build passes but the process exits immediately | The start command built but did not keep a worker pool alive — pip install … belongs in Build; the gunicorn line belongs in Start. |
See Deployment Troubleshooting and Logs for broader diagnosis.
Related documentation#
- Web Services — full public runtime contract across language stacks.
- Private Services — the same Python server on the workspace-private network.
- Environment Variables — scope secrets so
pip installandgunicorneach see the right values. - Domains and Networking — public vs private access.