Python App Hosting: FastAPI, Flask and Django
Deploy FastAPI, Flask and Django apps from a folder in your home directory. Each app gets its own virtualenv, a uvicorn or gunicorn service that runs as your Linux user and restarts on failure, and an optional Apache reverse proxy on a path or its own domain.
Python Apps (vPanel → Python Apps) runs a Python web app from a folder inside your home directory. Each app gets its own virtualenv, its own systemd service running as your Linux user, and — if you want it — an Apache reverse proxy on a path of your site or on one of your domains.
Two servers are offered, and the choice decides which of your frameworks will work:
- uvicorn — ASGI. FastAPI, Starlette, and anything else that speaks ASGI.
- gunicorn — WSGI. Flask, Django, Bottle, and other WSGI apps. It is started with
--workers 2.
Nothing else is accepted; the deploy is rejected if the server is not one of these two.
Before you deploy
Put your code in a folder inside your home directory using the File Manager, Git or SFTP over SSH, with a requirements.txt alongside it. The app runs in place from that folder.
The server needs working python3 -m venv support. If it is missing, the deploy fails with "Python virtualenv support is not installed — enable it first" — ask your administrator to install python3-venv (plus python3-dev and build-essential if any of your dependencies compile C extensions).
The app entry point
App entry must be written as module:variable — the same notation uvicorn and gunicorn use on the command line:
| Framework | App entry |
|---|---|
FastAPI in app/main.py with app = FastAPI() | app.main:app |
Flask in wsgi.py with application = Flask(__name__) | wsgi:application |
Django project mysite | mysite.wsgi:application |
Anything that is not module:variable is rejected before the service is written.
The virtualenv
The panel looks for venv/bin/python inside your app folder. If it is not there, it runs python3 -m venv venv as your account user, so every file in the virtualenv belongs to you.
With Create/refresh the virtualenv and install requirements.txt ticked, it then runs, again as your user:
venv/bin/pip install --upgrade pip wheel— 2 minute limitvenv/bin/pip install -r requirements.txt, if that file exists — 15 minute limitvenv/bin/pip install uvicorn(orgunicorn) — 5 minute limit
If pip fails, the last twelve lines of its output are shown with the error and no service is created.
Leaving the box unticked is fine for a redeploy where nothing changed, but the deploy will refuse to continue if venv/bin/uvicorn (or venv/bin/gunicorn) does not exist — install dependencies once, or add the server to your requirements.txt.
Ports and memory
Pick a port between 1024 and 65535; the page suggests a free one. Ports 21, 22, 25, 80, 110, 143, 443, 465, 587, 993, 995, 2083, 2087, 3306 and 8891 are reserved and rejected, and so is any port already claimed by another Vanta Python, Node or Java app.
The memory limit accepts 64–4096 MB (256 MB by default). It becomes a systemd MemoryMax of your value plus 64 MB.
What gets created
A systemd unit at /etc/systemd/system/vantapy-<account>-<app>.service:
User=<your account>
WorkingDirectory=<your app folder>
Environment=PORT=<port>
Environment=HOME=/home/<your-account>
Environment=PYTHONUNBUFFERED=1
ExecStart=<app folder>/venv/bin/uvicorn <module:variable> --host 127.0.0.1 --port <port>
Restart=on-failure
RestartSec=5
MemoryMax=<your limit + 64>M
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=fullWith gunicorn the ExecStart line is venv/bin/gunicorn <module:variable> --bind 127.0.0.1:<port> --workers 2 instead. The unit waits for the network and for MariaDB before starting, and it is enabled — your app comes back by itself after a reboot.
PYTHONUNBUFFERED=1 matters: it is why your print() output and tracebacks show up in the log view straight away instead of sitting in a buffer.
Note that the server binds to 127.0.0.1, not to 0.0.0.0. The app is never directly reachable from the internet; traffic always arrives through Apache.
Exposing the app
On a path of my site — a proxy fragment is written to /etc/apache2/vantajava/<account>/py-<app>.conf and included from every one of your account's Apache vhosts:
RedirectMatch 301 ^/api$ /api/
ProxyPass /api/ http://127.0.0.1:<port>/
ProxyPassReverse /api/ http://127.0.0.1:<port>/The prefix is stripped, so a request for /api/items reaches your app as /items. In FastAPI you can line the two up by setting root_path="/api" so the generated OpenAPI docs and links use the right prefix. Because the include goes into all of the account's vhosts (HTTP and HTTPS, primary domain, subdomains and addon domains), the path answers on every hostname on the account.
On its own domain — a dedicated vhost is written to /etc/apache2/sites-available/vantapy-<account>-<app>.conf, proxying / to your app with ProxyPreserveHost On and writing its own Apache access and error logs (<account>-py-<app>-*.log). The domain must already belong to your account. That vhost listens on port 80, so point the domain's DNS at this server first.
Internal only — this is the default on the form. No proxy is written and the app answers only on 127.0.0.1:<port>, which is what you want for a background worker or an internal service.
Every proxy change is validated with apache2ctl configtest before Apache is reloaded and rolled back if it fails, so a bad path cannot break your website.
Logs, restarts, and removing
Each app row has Logs, Restart, Stop/Start and Remove.
Logs reads the service journal (journalctl -u vantapy-<account>-<app>), last 200 lines, ISO timestamps — that is your app's stdout and stderr, including the traceback from a failed startup.
Redeploying with the same app name is how you ship new code: the unit is rewritten and the service restarted. Tick the dependency box if requirements.txt changed.
Remove stops the service, disables it, deletes the unit and removes any proxy configuration. Your app folder and its virtualenv are kept — nothing you uploaded is deleted.
Django notes
- Use gunicorn with
<project>.wsgi:application. - Django serves no static files in production. Run
venv/bin/python manage.py collectstaticfrom the Terminal, then either serveSTATIC_ROOTfrom your document root or addwhitenoiseto the app. - Put
ALLOWED_HOSTSright. With a dedicated domain the originalHostheader is preserved, so your own domain is what arrives. - Use the Databases page to create a MySQL database and connect to it on
127.0.0.1.
See also
- Node.js App Hosting — the same model for Node services.
- Java Apps — for executable JARs and Spring Boot.
- Cron Jobs — for scheduled work that is not a web request.