# Python

> A Python application serving its own HTTP, through Gunicorn or Uvicorn

A Python application serving its own HTTP, through a WSGI or ASGI server —
Gunicorn, Uvicorn. Like Node and Go it is the web server as well as the
application, and it replaces PHP-FPM and Nginx rather than joining them. In
front of it is the platform's proxy, which terminates TLS and routes the
hostname.

The image is the official Python image on Debian, slim, with uv, a compiler
and the MySQL and PostgreSQL client headers added, so a package that builds
from source — `mysqlclient` does — builds. Choose the version with
`runtime.python` in [`vallic.yaml`](configuration.md):

```yaml
version: 1
type: python

runtime:
  python: '3.14'

build:
  steps:
    - uv sync --locked --no-dev --no-editable
  cache:
    - .venv

start: python -m gunicorn app:app
port: 8000
```

**Run everything as `python -m …`** — the server, workers, cron and deploy
steps. The build installs into `.venv` in the checkout, and the release is that
checkout moved: `python` is the release's own wherever it runs, while a script
such as `bin/gunicorn` still names the build's path inside it. For the same
reason, install without `--editable`.

With no `start`, the image runs `python -m gunicorn`, which reads the
application from `gunicorn.conf.py` (`wsgi_app = "app:app"`). Gunicorn listens
on `PORT` — `8000` unless `port` says otherwise — on every address, and starts
`WEB_CONCURRENCY` workers, 2 unless you set it on the service (see
[Service settings](service-settings.md)). uv's and pip's download caches are
kept between builds for you, and so is `.venv` when the build names it.

The image's Python is the one used, whatever a `.python-version` file asks
for: the version is `runtime.python`.

Uploads and anything else written at runtime go under `/mnt/files`
(`VALLIC_PRIVATE_DIR`), which outlives the release. See
[Python](framework-python.md) for a fuller example, and
[Django](framework-django.md) for Django.

## Versions

| Version | Status |
| --- | --- |
| `3.15` | Supported |
| `3.14` | Supported, and the default |
| `3.13` | Supported |
| `3.12` | Supported |

Pin the version, not the build: name `3.14` and the platform matches it to the
current build, so a security rebuild reaches you without anybody editing a
repository.

## What you can change

In [`vallic.yaml`](configuration.md) — what each one does is on [Service settings](service-settings.md):

```yaml
services:
  - python:
      version: '3.14'
      environment:
        WEB_CONCURRENCY: …
```

Anything not on this list refuses the deploy, naming the variable — rather
than being accepted and quietly ignored.
