OctoPrint on Ubuntu, using Python 3 and systemd

Most OctoPrint installations out there, will, most likely, run with an ready baked OctoPi image file for the Raspberry Pi. This is obviously the most straight forward approach to get OctoPrint up and running. Still, there are good reasons to run it on a regular machine. You may already run a home server or other Linux system. You also may like to have more processing power, than the RasPi can offer. In the following article, I will describe how I set up OctoPrint on my existing small Linux server, running Ubuntu Server. Still, this should — in principle — work on any modern-day Linux distribution which employs systemd. Some distributions ship Python 3 with the venv module already included, so you don’t even need to install it explicitly.

Unlike older articles about this topic, this is new/different:

  • Uses Python 3.x ,since OctoPrint 1.4.0 introduced Python 3 support.
  • Uses Python Virtual Environments (venvs), like it is recommended by the creator(s) of OctoPrint.
  • Allows for multiple OctoPrint instances using systemd. The setup is the same for both single and multiple instances.

OctoPrint supports Python 3 now, but there are still many OctoPrint plugins not yet available. Please consider this, before upgrading or using it.

All following commands prefixed with a hash mark (root:#) need to be executed as root user, by either using sudo (recommended for Ubuntu) or by changing to the root account.

systemd unit file templates explained

systemd supports configuring and running multiple instances of the same software, by using so called Template Unit Files. These files only differ from regular unit files by using an instance parameter (%i). The unit files using such a parameter, are named, for example, octoprint@.service (Note the @ right before the file name extension!). Actual instances of these units need to use an unique identifier after the “@” symbol, like a name or a number. They are simply created by enabling a new service:

root:# systemctl enable --now octoprint@foo.service

In this example, every %i, inside the unit file, will be replaced by foo.

For the following setup I will use index numbers as instance parameters.

The pattern I want to implement looks like this. Note, that the suffix for the directory and the last digit of the port number are the same:

  1. Instance #1 uses subfolders octoprint1/instance1 and runs on port 5001
  2. Instance #2 uses subfolders octoprint2/instance2 and runs on port 5002
  3. Instance #3 uses subfolders octoprint3/instance3 and runs on port 5003
  4. […]

Please note, that although you could use other identifiers for this, like strings, it wouldn’t work because the network port needs to be a number.

Preparing Python

The Ubuntu 18.04 LTS version I’m currently running, still comes with Python 2.7 as the default. You want to make sure to actually have Python 3 installed, not (only) Python 2. Both interpreters can be installed and used in parallel. Also the Python 3 venv module comes in a separate package on Debian/Ubuntu systems and needs to be installed explicitly:

root:# apt install python3 python3-pip python3-dev python3-setuptools python3-venv git build-essential
root:# python3 --version
Python 3.6.9
root:# which python3
/usr/bin/python3

The last two commands are only for testing if Python 3 is installed and available — the actual minor version may vary on your system.

Creating a new user

Now, we add a new user for running OctoPrint. This allows for a better control of what the service is allowed to do and what it’s not. I simply added a new regular user account in /home, called octoprint. Also make sure to add the new user to the user groups tty and dialout (see below). This will make sure that the user has permission to access the USB-to-UART serial devices most 3D printers use. Obviously you may call the user however you like. Just make sure to replace the name octoprint with your own, in all the following commands.

First the actual user will be created. The password can be a random string, since the account will be locked and the password disabled anyway.

root:# adduser octoprint
root:# passwd -l octoprint
root:# usermod -a -G tty octoprint
root:# usermod -a -G dialout octoprint

After executing these commands, there should be a /home/octoprint directory and some basic (hidden) files in it.

Next, we use the su command to change to the user octoprint and verify that we are in the correct (home) directory of that user.

root:# su - octoprint
octoprint:$ cd
octoprint:$ pwd
/home/octoprint

Another option would be to add a system user with a home directory in, for example, /srv/octoprint. System users don’t have passwords and can’t login regularly. In this case a shell needs to be provided when using the su command: su - -s /bin/bash octoprint.

Creating a Python Virtual Environment and installing OctoPrint

A Virtual Environment in Python allows for isolation between different running Python scripts and their libraries and used packages. Each environment is basically a (automatically created and managed) directory tree. This allows for installing different packages and/or versions in each environment. Even if only one OctoPrint instance is used, it is recommended to put it into a virtual environment.

I decided to put all these Python environments into a directory named python_venvs in the home directory of the user octoprint and name them octoprint followed by a index number (for example: octoprint1). As noted above: The matching index number is important for managing the service(s) with systemd later.

octoprint:$ mkdir /home/octoprint/python_venvs

Next, a new virtual environment in the directory /home/octoprint/python_vdevs/octoprint1 will be created by running the following command:

octoprint:$ cd /home/octoprint/python_venvs
octoprint:$ python3 -m venv octoprint1

The next step is to change into the environment, update the pip package and then use pip to install OctoPrint into the environment. Repeat these steps for every instance you plan on running.

octoprint:$ source octoprint1/bin/activate
(octoprint1) octoprint:$ pip install --upgrade pip
(octoprint1) octoprint:$ pip install octoprint
(octoprint1) octoprint:$ deactivate

Please don’t start OctoPrint just yet, since we need to do some other things first.

What does “activate” and “deactivate” do? It basically puts the bin folder inside the virtual environment at the beginning of the PATH environment variable. The command also changes the prompt to show the user which venv is currently used. “deactivate” returns to the regular Python environment of the user.

Creating a directory for configuration files and uploads

Every instance needs a directory for the configuration files and other files, like uploaded files, also called the basedir. I decided to create another directory for all these directories, named octoprint_basedirs in /home/octoprint.

Now create at least one directory inside of it – in this case instance1. Please make sure, that the index number of the virtual environment and the basedir match.

octoprint:$ mkdir /home/octoprint/octoprint_basedirs
octoprint:$ mkdir /home/octoprint/octoprint_basedirs/instance1

If you want to setup multiple instances you may add additional subfolders now (like instance2, instance3, …).

Configuring systemd

Create a new unit file template called octoprint@.service in the /etc/systemd/system directory and paste the following content into it:

[Unit]
Description=OctoPrint (instance #%i) - Open Source Printing Interface for 3D Printers
Documentation=https://docs.octoprint.org
After=network.target

[Service]
User=octoprint
Environment=HOME=/home/octoprint
WorkingDirectory=/home/octoprint/octoprint_basedirs/instance%i
ExecStart=/home/octoprint/python_venvs/octoprint%i/bin/python3 /home/octoprint/python_venvs/octoprint%i/bin/octoprint --basedir /home/octoprint/octoprint_basedirs/instance%i --config /home/octoprint/octoprint_basedirs/instance%i/config.yaml --port 500%i serve
Restart=always
RestartSec=100

[Install]
WantedBy=multi-user.target

The following commands will re-read the systemd configuration files, enable the octoprint@1.service and also start it immediately and every time the system (re)starts.

root:# systemctl daemon-reload
root:# systemctl enable --now octoprint@1.service

You may assure yourself, if OctoPrint is running, by running systemctl status octoprint@1.service. Also with journalctl -u octoprint@1.service you may review the log messages OctoPrint printed (and prints) to the console while starting (running).

To add another instance, you only need to add a matching virtual environment (like described above) and replace the instance parameter in the systemctl commands above. For example: systemctl enable --now octoprint@2.service

Why is the virtual environment not “activate”ed by systemd, like described above? These script are only for convenience, to make the usage of venvs easier to human users. Since the Python interpreter has built in support for virtual environments, it realizes this by itself when a script inside a venv is called – like in this case – by systemd.

Testing the setup

At this point, the OctoPrint service should start up and allow you to access the web interface on the port configured (i.e. 5001 for the first instance). If it doesn’t work, the first step should be to check the status and logfile of the systemd service. Maybe there is just a typo in the unit file.

root:# systemctl status octoprint@1.service
root:# journalctl -u octoprint@1.service -n 100 --no-pager

If the service doesn’t start and it has nothing to do with systemd, the next step would be to manually start the OctoPrint script in the context of the user octoprint:

root:# sudo su - octoprint
octoprint:$ source ~/python_venvs/octoprint1/bin/activate
(octoprint1) octoprint:$ octoprint serve

This will start OctoPrint with the default settings. Try accessing the service on the http port 5000.

Editing sudoers.d

If you want OctoPrint to be able to restart itself and/or shutdown/restart the whole system, you need to configure the sudo command. Create a new file called octoprint with the following content in the /etc/sudoers.d directory:

# octoprint ALL = NOPASSWD: /bin/systemctl start octoprint@[0-9].service
# octoprint ALL = NOPASSWD: /bin/systemctl stop octoprint@[0-9].service
octoprint ALL = NOPASSWD: /bin/systemctl restart octoprint@[0-9].service
# octoprint ALL = NOPASSWD: /bin/systemctl reboot
# octoprint ALL = NOPASSWD: /bin/systemctl poweroff

In this case, only one line is active, all others have been commented out, because I personally don’t need/want them. If you do, please remove the # in front of the lines, but keep in mind, that this may or may not be a security issue on your system. If you are unsure, don’t uncomment any more lines for now.

Now, in the OctoPrint web interface, you can go to Settings, Server, Commands and enter the necessary commands OctoPrint should execute, when the matching menu entry is selected. Don’t forget to add the sudo in front of the command. For example:

sudo systemctl restart octoprint@1.service

If you want Octoprint to be able to restart (and/or shutdown) the system, you need to add the corresponding line(s) in the /etc/sudoers.d/octoprint file by removing the “#” in front of it and enter the command using the sudo command in the settings, like described in the paragraph above.

Using different names and paths

If you prefer your own names and folder structure, feel free to change them as you like. Things you can change:

  • Username of the user running OctoPrint
  • Directory for all the virtual environments
  • Directory for all the OctoPrint basedirs
  • Name of the systemd service*
  • The http port range*

* Since the last digit of the port number uses the systemd instance parameter and the whole setup is based on that assumption, you can only change the numbers to other ones and never use the same number twice.

For example: Use the port range 8010-8050 with an increment of 10 per instance or something similar.

Using Python 2

You may still want to use Python 2 instead of Python 3. Although I’ve not tested this, it should work more or less the same way. Please note, that Python 2 uses a different syntax for creating virtual environments. Also the package for this needs to be installed first.

root:# apt install virtualenv
root:# sudo su - octoprint
octoprint$: cd
octoprint$: mkdir python_venvs
octoprint$: cd python_venvs
octoprint$: virtualenv octoprint1
octoprint$: source octoprint1/bin/activate
(octoprint1) octoprint$: pip install --upgrade pip
(octoprint1) octoprint$: pip install octoprint
(octoprint1) octoprint$: deactivate

Migrating from Python 2 and/or single instance systemd setups

The following assumes, that you have used one of my other articles to setup OctoPrint with systemd or OctoPrint with multiple instances using systemd. If not, you may need to adapt the commands to your naming and paths used.

First, stop any running instance of OctoPrint.

For example:

root:# systemctl stop octoprint.service

or

root:# systemctl stop octoprint@1.service
root:# systemctl stop octoprint@2.service

If you have used the single instance setup using systemd, also remove the existing service:

root:# systemctl disable octoprint.service

You may also delete the unit file octoprint.service from /etc/systemd/system. It’s not needed anymore.

Then move the basedir and its content of every existing OctoPrint instance to the correct place in the new file structure, used in this article. For example:

octoprint:$ cd
octoprint:$ mv .octoprint octoprint_basedirs/octoprint1

or

octoprint:$ cd
octoprint:$ mv .octoprint1 octoprint_basedirs/octoprint1
octoprint:$ mv .octoprint2 octoprint_basedirs/octoprint2

(Re-)create or edit the octoprint@.service file /etc/systemd/system with the following content (this is the exact same file content as shown in the article above):

[Unit]
Description=OctoPrint (instance #%i) - Open Source Printing Interface for 3D Printers
Documentation=https://docs.octoprint.org
After=network.target

[Service]
User=octoprint
Environment=HOME=/home/octoprint
WorkingDirectory=/home/octoprint/octoprint_basedirs/instance%i
ExecStart=/home/octoprint/python_venvs/octoprint%i/bin/python3 /home/octoprint/python_venvs/octoprint%i/bin/octoprint --basedir /home/octoprint/octoprint_basedirs/instance%i --config /home/octoprint/octoprint_basedirs/instance%i/config.yaml --port 500%i serve
Restart=always
RestartSec=100

[Install]
WantedBy=multi-user.target

Now create a new Python 3 virtual environment and install OctoPrint into it, like described above. For example:

octoprint:$ cd
octoprint:$ mkdir python_venvs
octoprint:$ cd python_venvs
octoprint:$ python3 -m venv octoprint1
octoprint:$ source octoprint1/bin/activate
(octoprint1) octoprint:$ pip install --upgrade pip
(octoprint1) octoprint:$ pip install octoprint
(octoprint1) octoprint:$ deactivate

Repeat this for every instance. Make sure, that the index number and naming scheme matches the folder structure described above. If you have only one instance, the names and paths of the folders are /home/octoprint/octoprint_basedirs/instance1 and /home/octoprint/python_vdevs/octoprint1. The name of the systemd service will be octoprint@1.service.

Now enable and start the systemd service.

root:# systemctl enable --now octoprint@1.service

If you have configured OctoPrint before, to use the --user flag, when calling pip, disable this option in the plugin manager, by using the OctoPrint web interface!

Multiple 3D printers connected to the same system

If you have multiple instances of OctoPrint running, you will have, most likely, multiple 3D printers connected to your Linux (server) system. In this case I recommend to check out my other article about adding named symbolic links to the device directory.

There are also many settings available in the config.yaml of OctoPrint related to the serial port connections on your system. You may, for example, set a default port and/or explicitly add additionalPorts or patterns.

10 Replies to “OctoPrint on Ubuntu, using Python 3 and systemd”

  1. Thank you very much for this guide. Been away from linux sysadmin for a few years (retired tech business owner), and I had put most of the pieces together this week but not enough to get anything working, until I found your guide and it filled in a couple of critical gaps in my knowledge. Your guide is very clear, very easy to follow — and it all worked flawlessly (Ubuntu 20.04).

    I’m just (almost!) getting started w/ 3D printing: I have the printer built, the software control is set-up, but I’m still waiting on filament! Pretty sure the bed is about as level as I can get it… 😉

    1. Thank you very much for this how to, this is really great and worked for me flawlessly on Ubuntu 20.04 with Python 3.8.2.

      But i have a problem, i can´t get my USB Webcam to work….

      Do i need a special solution for it to work on python-venvs?

      Can you please provide any help?

      THANKS

      1. Please check out my comment below! As far as I know accessing the webcam shouldn’t change in any why because of the Python Virtual Environment(s). The stream needs to work without OctoPrint first.

  2. I’m not going to lie, I have no idea what most of these commands do even though you explained it quite well.
    But with all said and done it works great on Ubuntu 20.04, thanks for the help. Would never have gotten it setup myself.

  3. I followed the tutorial and everything works except the webcam, did it work for you? By default under webcam there is no default stream url, I entered
    /webcam/?action=stream
    But nothing is displayed.

    1. The webcam needs to be setup before using it in OctoPrint. I haven’t used my cam in a while, but what I used is “mjpg-streamer” which I installed as a snap package on my Ubuntu home server. This needs to run and be accessible via URL in the web browser. After that, the said URL can be entered into the OctoPrint configuration. So bacially OctoPrint only includes the existing stream but does not access the cam hardware by itself.

Leave a Reply

Your email address will not be published. Required fields are marked *