pyproject.toml · v1.0.4

version
1.0.4
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.1 via paramiko
  • decorator 5.3.1 via fabric
  • deprecated 3.0.0 via fabric
  • fabric 3.2.3
  • invoke 2.2.1 via fabric, paramiko
  • paramiko 5.0.0 via fabric
  • pycparser 3.0 via cffi
  • pynacl 1.6.2 via paramiko
  • pytypehint 1.2.1
  • wrapt 2.5.0 via deprecated

# pyeasydeploy

Deploy Python apps to Linux servers over SSH, with plain Python functions on top of Fabric: no agents on the server, no YAML, no DSL to learn. Your deploy is a deploy.py in your project that reads top to bottom.

It is made for a few servers and a team that writes Python, not to compete with Ansible or Docker.

from pyeasydeploy import (
    Host, Key, SupervisorService, connect, create_venv,
    deploy_supervisor_service, get_target_python_instance,
    install_local_package, supervisor_restart,
)

APP = "myapp"
USER = "deploy"

conn = connect(Host(
    address="203.0.113.10",
    user=USER,
    auth=Key(path="~/.ssh/id_ed25519"),
    sudo_password="...",   # better: os.environ["SUDO_PASSWORD"]
))

py = get_target_python_instance(conn, "3.11")
venv = create_venv(conn, py, f"/home/{USER}/venvs/{APP}")
install_local_package(conn, venv, f"./{APP}")

deploy_supervisor_service(conn, SupervisorService(
    name=APP,
    command=f"{venv.path}/bin/python -m {APP}",
    directory=f"/home/{USER}",
    user=USER,
))
supervisor_restart(conn, APP)

# What a deploy does

The introductory example is a complete deploy. Each call is one step, run over SSH in order:

  1. connect opens a connection to the Host: its address, user, and either a Password or a Key. Operations that need sudo use sudo_password, or the SSH password when auth is a Password. The connection is lazy: a wrong password shows up on the first command.
  2. get_target_python_instance picks an interpreter already installed on the server: the newest one whose version matches "3.11".
  3. create_venv builds a virtual environment with that interpreter, removing any previous one at the same path.
  4. install_local_package uploads your local package and installs it in the venv with its dependencies.
  5. deploy_supervisor_service writes a supervisord entry for the app and reloads supervisord, so the app runs as a service that restarts on failure and survives reboots. supervisor_restart then restarts it, so it runs the new code even when its configuration did not change.

The venv object returned by create_venv carries its own path, so the service command is built from it and no path is repeated by hand. Run the script again and the server ends up in the same state: see Reproducibility.

The models (Host, Key, SupervisorService and the rest) validate themselves when built, so a relative path or a malformed service name fails on your machine before anything reaches the server. Keep secrets out of the script: read sudo_password from the environment, for example os.environ["SUDO_PASSWORD"].

# Requirements

  • Your machine: Windows, macOS or Linux, with SSH access to the server.
  • The server: Linux with SSH and a python3 in /usr/bin. Tested on Arch, Debian and Ubuntu.
  • Services: supervisord is installed and managed on Arch and on Debian-based distributions (Debian, Ubuntu), detected from /etc/os-release. Managing services needs sudo on the server.

# 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.