# Python Quickstart

> Deploy a Python HTTP application as a NexHost Web Service.

Source: https://nexthomelabs.com/docs/quickstarts/python
Markdown: https://nexthomelabs.com/docs-md/quickstarts/python
Slug: quickstarts/python
Section: Quickstarts
Last updated: 2026-08-30
Reading time: 5 min read

---

# Python Quickstart

Use a [Web Service](/docs/services/web-services) 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](/docs/services/private-services).
- The process is a persistent asynchronous consumer with no HTTP — choose [Background Worker](/docs/services/background-workers).
- The process should run on a schedule and exit — choose [Cron Job](/docs/services/cron-jobs).

## 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`:

```python
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 to `127.0.0.1` makes TCP readiness observe "connection refused" even when your direct localhost test passed.
- `/health` is optional application monitoring; NexHost deployment readiness is based on TCP reachability.

Create `requirements.txt` in the same directory:

```text
Flask
gunicorn
```

`Flask` 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.

> [!TIP]
> 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:

```python
# 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`:

```text
fastapi
uvicorn
```

The platform-observed difference is only the **start command**: Flask uses `gunicorn`, FastAPI uses `uvicorn`.

## Configure the service

1. Select **Web Service** in **New Project** — not Frontend App and not Private Service.
2. Connect the **repository or archive** containing `app.py` and `requirements.txt`. Verify the branch and root directory when the dashboard shows them.
3. 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.
4. Set **Start command** to `gunicorn app:app --bind 0.0.0.0:$PORT`. This keeps workers alive and routes them at the injected port.
5. 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`:

```bash
uvicorn app:app --host 0.0.0.0 --port $PORT
```

The 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>/health` returns `{"ok": true}` quickly. Opening `https://<hostname>/` returns the greeting.
- A fresh deployment after updating only an environment variable produces the updated `/health` response 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](/docs/deployments/troubleshooting) and [Logs](/docs/operations/logs) for broader diagnosis.

## Related documentation

- [Web Services](/docs/services/web-services) — full public runtime contract across language stacks.
- [Private Services](/docs/services/private-services) — the same Python server on the workspace-private network.
- [Environment Variables](/docs/configuration/environment-variables) — scope secrets so `pip install` and `gunicorn` each see the right values.
- [Domains and Networking](/docs/configuration/networking) — public vs private access.

