Debian 13 Server: Pyenv, FastApi and Virtual Environments

This post describes how to set up a Debian 13 machine to run FastAPI applications with option to have different Python versions and virtual Python environments. This post does NOT contain detailed step-by-step instructions for the setup, only the parts that I consider important to document.

Server target configuration:

  1. Should support a number of websites that will be hosting FastAPI applications.
    1. Each application should be able to use it's own Python version and packages.
  2. The system state should be as close to the stock one as possible.
    1. There should be NO non-system files in folders where system files are present.
      1. Exceptions like website configurations and systemd services definitions are acceptable.
    2. There should be only one system package per function (e.g. no two web servers doing the same).

Location

All files will reside in /srv according to Filesystem Hierarchy Standard:

/srv/pyenv                           # Home for Pyenv
/srv/systemd-services # Systemd scripts
/srv/apache2-configs # Apache config files

/srv/example.com/ # Container for the domain
/srv/example.com/venv # Root domain Python virtual environment
/srv/example.com/backend # Root domain backend
/srv/example.com/frontend # Root domain frontend

/srv/example.com/subdomain1/ # Container for the subdomain
/srv/example.com/subdomain1/venv # Subdomain Python virtual environment
/srv/example.com/subdomain1/backend # Subdomain backend
/srv/example.com/subdomain1/frontend # Subdomain frontend

...

Pyenv Setup

Debian has a package for Pyenv:

sudo apt-get install pyenv

I could not find any guides on Pyenv setup for server environment, so we need to adapt the regular approach to let admin and www-data users to use it.

As Pyenv will be used at least by 2 users, it makes sense to add it's Bash configuration to the end of /etc/profile to be available system-wide:

# Using 'tee' because the redirection >> is performed by the user’s shell (that is not root)

echo '
# Pyenv configuration
export PYENV_ROOT="/srv/pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init -)"
' | sudo tee --append /etc/profile

You will see the effect after you login back to the machine.

However, users without shell like www-data will not receive the configuration in the above way. We will setup www-data to use the custom Python in the systemd file below.

# Setup custom Python version
cd /srv/example.com/subdomain1/
pyenv install 3.12
pyenv local 3.12

Virtual Environments

pyenv virtualenv is not a part of Debian pyenv and we will use standard Python venv:

# Setup virtual environment
python -m venv /srv/example.com/subdomain1/venv
source /srv/example.com/subdomain1/venv/bin/activate

Systemd Services

To use Python from the virtual environment we will invoke it directly:

# /srv/systemd-services/uvicorn-subdomain1.service
[Unit]
Description=FastAPI / Uvicorn – Subdomain1
After=network.target

[Service]
Type=simple

User=www-data
Group=www-data

WorkingDirectory=/srv/example.com/subdomain1/backend/current

Environment=PATH=/srv/example.com/subdomain1/venv/bin:$PATH
ExecStart=/srv/example.com/subdomain1/venv/bin/python -m uvicorn main:app --host 0.0.0.0 --port 8000

Restart=on-failure
RestartSec=10

[Install]
WantedBy=multi-user.target

Apache Configs

Apache looks for domain names in the order of appearance, so if it loads the top domain configuration first, the subdomains will not receive traffic.