How to enable a virtualenv in a systemd service unit?

PythonEnvironment VariablesVirtualenv

Python Problem Overview


I want to "activate" a virtualenv in a systemd service file.

I would like to avoid having a shell process between the systemd process and the python interpreter.

My current solution looks like this:

[Unit]
Description=fooservice
After=syslog.target network.target

[Service]
Type=simple
User=fooservice
WorkingDirectory={{ venv_home }}
ExecStart={{ venv_home }}/fooservice --serve-in-foreground
Restart=on-abort
EnvironmentFile=/etc/sysconfig/fooservice.env

[Install]
WantedBy=multi-user.target

/etc/sysconfig/fooservice.env

PATH={{ venv_home }}/bin:/usr/local/bin:/usr/bin:/bin
PYTHONIOENCODING=utf-8
PYTHONPATH={{ venv_home }}/...
VIRTUAL_ENV={{ venv_home }}

But I am having trouble. I get ImportErrors since some entries in sys.path are missing.

Python Solutions


Solution 1 - Python

The virtualenv is "baked into the Python interpreter in the virtualenv". This means you can launch python or console_scripts directly in that virtualenv and don't need to activate the virtualenv first or manage PATH yourself.:

ExecStart={{ venv_home }}/bin/fooservice --serve-in-foreground

or

ExecStart={{ venv_home }}/bin/python {{ venv_home }}/fooservice.py --serve-in-foreground

and remove the EnvironmentFile entry.

To verify that it is indeed correct you can check sys.path by running

{{ venv_home }}/bin/python -m site

and comparing the output to

python -m site

Solution 2 - Python

While the path for libraries is indeed baked into the python interpreter of the virtualenv, I've had issues with python tools that were using binaries installed in that virtualenv. For instance, my apache airflow service wouldn't work because it couldn't find the gunicorn binary. To work around this, here's my ExecStart instruction, with an Environment instruction (which sets an environment variable for the service alone).

ExecStart={{ virtualenv }}/bin/python {{ virtualenv }}/bin/airflow webserver
Environment="PATH={{ virtualenv }}/bin:{{ ansible_env.PATH }}"

ExecStartexplicitly uses the python interpreter of the virtualenv. I'm also adding a PATH variable, which adds the binary folder of the virtualenv before the system PATH. That way, I get the desired python libraries as well as binaries.

Note that I'm using ansible to build this service, ergo the curly braces of jinja2.

Solution 3 - Python

I'm not using virtualenv but pyenv: here is it just to use the real .pyenv path in the shebang and make sure it is in the PATH

Ex: pyenv activate flask-prod for user mortenb which is running in prod

/home/mortenb/.pyenv/versions/flask-prod/bin/python --version
Python 3.6.2

Then to my flask scripts starting in systemd *.service I add the following shebang:

#!/home/mortenb/.pyenv/versions/flask-prod/bin/python3

Solution 4 - Python

In my case I just tried to add environment variables required for Flask, for instance

[Service]
Environment="PATH=/xx/yy/zz/venv/bin"
Environment="FLASK_ENV=development"
Environment="APP_SETTINGS=config.DevelopmentConfig"

I was using virtualenv so /xx/yy/zz/venv/bin is the path of virtualenv folder.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionguettliView Question on Stackoverflow
Solution 1 - PythonNils WernerView Answer on Stackoverflow
Solution 2 - PythonAlexis LessardView Answer on Stackoverflow
Solution 3 - PythonMortenBView Answer on Stackoverflow
Solution 4 - PythonSebastian Cardona OsorioView Answer on Stackoverflow