Custom Apps

How to Deploy a Python App on a VPS Without a Dockerfile

A requirements file and one start command. Or a uv lockfile, if you want the install pinned down to the hash.

A Python web service usually needs a container file that nobody wants to own. Impreza generates it: choose the strategy python_pip, give the platform a flat requirements.txt and the command that starts your server, and it builds Python 3.13 with a virtual environment and a non root user.

If your project already uses uv, you can pin the install instead, with pyproject.toml and uv.lock.

What you get

  • Python 3.13 in a virtual environment, built without a container file
  • Two dependency paths: a flat requirements.txt, or a locked uv project
  • Your own start command, run as a non root user
  • Port 8000 by default, with PORT and HOST supplied to the process
  • Health checked from inside the container, requiring 2xx with no redirect

Choose the dependency path

requirements.txt is the default and the simplest. It has to be a flat list of PyPI package names with optional extras and version specifiers, up to 32 KB. Options such as -r includes, --index-url lines, direct URLs, local paths, @ references and line continuations are all refused. That restriction is what lets the build run with no network access beyond public PyPI and no chance of a requirements file redirecting the install somewhere else.

uv is the pinned path. Set python_package_manager to [email protected] and supply pyproject.toml with uv.lock. The build runs a locked, non editable production install, so what lands in the image is exactly what the lockfile resolved. Custom uv sources, extra indexes, workspaces and resolution settings are refused; those need a Dockerfile.

What neither path can do

Neither installs system packages. A wheel that needs a compiler, a system library or a headless browser at build time is outside both recipes. Write a Dockerfile for those, which is a supported deployment mode, not a fallback.

Deploy it

Write the start command

This recipe has no default. Give it the exact production command, on one line, and bind to the supplied port:

gunicorn app:app --bind 0.0.0.0:$PORT

For an async framework, the equivalent is uvicorn main:app --host 0.0.0.0 --port $PORT. Never put a credential in this field.

Pick the folder

If the repository holds more than one project, set project_dir to the subfolder holding the dependency file. Hidden folders, parent segments and symlinked path components are rejected.

Analyze first

Send requirements.txt and the start command to the project analyzer, through the portal or your AI agent. It returns framework hints and the findings that would block the build, without fetching your repository or executing anything.

Create the deployment

Create a custom deployment with build strategy python_pip, your source, and the start command. Add python_package_manager only if you are using uv. Keep 8000 unless the app needs another port.

Set the health path

Point healthcheck_path at a route that returns 200 by itself. Add require_healthy_start so a release that never answers is treated as a failure instead of a success.

Django and Flask often answer the root path with a redirect

The probe requires a 2xx status and does not follow redirects. An app that sends / to /login or to a language prefix fails the check even though it is running perfectly. Add a small route that returns 200 with no side effects, and point the health path at it.

Migrations are a separate step, on purpose

The deploy never touches your database. Run migrations after the release is live, through app inspection and maintenance or as a scheduled task, and take a backup first. Release history can restore an image; it cannot restore a table you dropped.

After it is running

Read the runtime health of the containers, which the platform reports separately from the result of the last operation, and remember that an observation has a freshness window: a stale reading is reported as unknown rather than guessed. Reading current health needs agent 0.6.4 or newer.

When you redeploy, the start command, health path and startup policy are inherited, so later releases are one action. If a replacement fails, an eligible healthy previous release can be recovered while volumes are preserved. See recovering from a failed deploy.

Start now

Get an offshore VPS with the agent preselected, wire up automatic deploys on git push, or read the other recipes: Node, PHP and static sites.

Ready to build privacy-first?

No KYC, no email required, crypto payment. Deploy an offshore server in minutes, or do it all by chat with the Impreza agent.