> ## Documentation Index
> Fetch the complete documentation index at: https://docs.withnubo.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Python

> Ship a Python app to Nubo

Python isn't covered by [Raypacks](/raypacks/overview) yet, so Python Frames use the classic Paketo builder. Switch the Frame's **Builder** to **Paketo** in **Frame Settings** if it's not already.

What Paketo needs from you:

1. A `requirements.txt` (or `Pipfile` / `pyproject.toml`).
2. A `Procfile` with a `web:` line that boots your server.
3. Your server binds to `0.0.0.0` on the port you set in the Frame.

If those are in place, [skip to Deploy](#deploy).

## Quick start (Flask + Gunicorn)

```bash theme={null}
mkdir hello-py && cd hello-py
```

`app.py`:

```python app.py theme={null}
import os
from flask import Flask

app = Flask(__name__)

@app.route("/")
def root():
    return "Hello from Python on Nubo!"

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=int(os.environ.get("PORT", 8080)))
```

`requirements.txt`:

```text requirements.txt theme={null}
flask
gunicorn
```

`Procfile`:

```text Procfile theme={null}
web: gunicorn --bind 0.0.0.0:${PORT:-8080} app:app
```

The `${PORT:-8080}` binds `PORT` if you've set it as a Frame variable, otherwise it falls back to `8080`. Nubo doesn't propagate the Frame's port setting into a `PORT` env var, so to run on a non-`8080` port, add a `PORT` variable in Frame Settings and set the Frame's port to match (or just hardcode the port here).

Test locally:

```bash theme={null}
pip install -r requirements.txt
PORT=8080 gunicorn --bind 0.0.0.0:$PORT app:app
```

## Deploy

<Steps>
  <Step title="Push to GitHub">
    Commit `app.py`, `requirements.txt`, and `Procfile`. Push.
  </Step>

  <Step title="New Frame on Nubo">
    Hit **+ Frame** in your Project, connect the repo.
  </Step>

  <Step title="Confirm the port">
    Match what your Procfile binds to.
  </Step>

  <Step title="Deploy">
    First build takes a minute or two while Paketo resolves dependencies.
  </Step>
</Steps>

## Pinning a Python version

Paketo picks a recent Python by default. To pin a version, add `project.toml`:

```toml project.toml theme={null}
[project]
id = "python-starter"
name = "Python Starter"
version = "1.0.0"

[[build.env]]
name = "BP_CPYTHON_VERSION"
value = "3.12.*"
```

## Things that trip people up

* **`flask run` in your Procfile.** That's the dev server. Use Gunicorn (or Uvicorn for async apps).
* **Binding to `127.0.0.1`.** Bind to `0.0.0.0` so Nubo's router can reach your app.
* **Port mismatch.** Nubo doesn't inject the Frame's port as `PORT`. Either hardcode the port your app binds or set a `PORT` variable in Frame Settings, and make sure the Frame's port matches what the app binds.
* **No `Procfile`.** Paketo needs a `web:` line to know what to launch.

## Raypacks support

Python is on the Raypacks roadmap. We'll add a Python pack once we're confident the build flow is fast and predictable. For now, Paketo is the path.
