pyproject.toml · v2.0.0

version
2.0.0
python
>=3.11
license
MIT
dependencies 2
installs 12
2 declared, 10 pulled in by them · python 3.13, linux
  • bcrypt 5.0.0 via paramiko
  • cffi 2.1.1 via cryptography, pynacl
  • cryptography 50.0.2 via paramiko
  • decorator 5.3.1 via fabric
  • deprecated 3.0.1 via fabric
  • fabric 3.2.3
  • invoke 2.2.1 via fabric, paramiko
  • paramiko 5.1.0 via fabric
  • pycparser 3.12 via cffi
  • pynacl 1.6.2 via paramiko
  • pytypehint 1.2.3
  • wrapt 2.5.2 via deprecated

# pyeasydeploy

Deploy a Python app to a Linux server with a few lines of Python and no sudo. The server only needs SSH and systemd: the deploy brings everything else.

from pyeasydeploy import App, Key, Server, deploy_app, ensure_python, ensure_uv

server = Server(address="203.0.113.10", user="deploy", auth=Key(path="~/.ssh/id_ed25519"))
python = ensure_python(ensure_uv(server), "3.13")
deploy_app(python, App(name="web", source="./web", command="web"))

That is the whole deploy, and it:

  • Brings its own Python. It uploads uv, which installs the Python you ask for. The distribution, its packages and its Python never matter.
  • Installs exactly your uv.lock. The server never picks versions on its own.
  • Keeps the app running while it installs. Each deploy is a new release, switched to with one restart. A failed install leaves the running one untouched.
  • Runs it as a service. systemd keeps it alive and starts it at boot. Its output goes to the journal, which rotates on its own.
  • Stays in its folder. Everything lives in ~/.pyeasydeploy, and nothing outside it is ever removed.

Immutable values and plain functions on top of Fabric, made for a few servers and a team that writes Python.

# What a deploy does

The introductory example is a complete deploy. Each call is one step, run over SSH in order, and each returns the value the next one takes:

  1. Server describes the server: its address, user, and either a Password or a Key. Nothing connects yet; the value is only validated. The library opens one SSH connection per Server the first time a function needs it, and reuses it.
  2. ensure_uv puts the uv binary in ~/.pyeasydeploy/bin/uv. It asks GitHub for the latest release and compares it with uv --version on the server: when they match, nothing is downloaded. Otherwise your machine downloads the build for the server's processor (x86_64 or aarch64) and uploads it.
  3. ensure_python has uv install the Python you ask for in ~/.pyeasydeploy/python/, upgraded to its latest patch release. The system's Python is never used: a distribution upgrade cannot change or remove the interpreter your app runs on.
  4. deploy_app packs your project, uploads it to a new release directory, and runs uv sync --frozen there, so the server gets exactly the versions of your uv.lock. Then it points current at the new release, writes a systemd user service for the app, and restarts it. The two newest releases are kept.

The app runs while the new release installs, and stops only for the restart. Its output goes to the systemd journal, which rotates it on its own: read it with app_logs.

On the server, everything lives in one folder, and the library refuses to remove anything outside it:

~/.pyeasydeploy/
  bin/uv
  python/                         Pythons installed by uv
  cache/                          uv's cache
  apps/web/
    releases/20261108T183000Z/    the code and its .venv
    current -> releases/20261108T183000Z
    pyeasydeploy-web.service      the systemd user service

# Requirements

  • Your machine: Windows, macOS or Linux, with SSH access to the server and to GitHub.
  • Your project: a pyproject.toml and a uv.lock (run uv lock in it).
  • The server: Linux on x86_64 or aarch64, with SSH, systemd and access to GitHub and PyPI. No Python, no packages, no sudo.
  • Once, as root on the server: loginctl enable-linger deploy (with your user), so the user's services start when the server boots and keep running after you log out. deploy_app checks it first and tells you the command if it is missing.

# Where to go next

  • Guide: each part of a deploy in detail, with the options that change it.
  • Reference: every public name.
  • Reproducibility: how far "same script, same server" reaches.
  • Design: the principles, and what the library deliberately does not do.