CloudPanel:Part 11 (Python)

Python deployment with uwsgi

Python deployment with uwsgi

On this site, you will find a step-by-step guide on deploying a Python Application using uWSGI with NGINX.

Creating a Python Site

Create a Python Site with the right Python Version and ensure the App Port is correct.

Creating a uWSGI Config

For running a Python Application in production, we use The uWSGI Project, which ensures that our Application is available after a reboot.

  1. Log in via SSH with the Root User and go to the uwsgi apps directory:
cd /etc/uwsgi/apps-enabled/
  1. Create a configuration file like domain.uwsgi.ini and modify the example configuration below to your needs:

uWSGI Configuration

Change the values of socket, wsgi-file, uid, and gid. You may want to look at the official uWSGI Documentation for further settings. On the Django Documentation, you find helpful information about deploying Django Applications via uWSGI.
[uwsgi]
plugins       = python3
master        = true
protocol      = uwsgi
socket        = 127.0.0.1:8090
wsgi-file     = /home/site-user/htdocs/python-project/wsgi.py

# In case you're using virtualenv uncomment this:
#virtualenv = /home/site-user/htdocs/python-project

# Needed for OAuth/OpenID
buffer-size   = 8192

# Reload when consuming too much of memory
reload-on-rss = 250

# Increase number of workers for heavily loaded sites
workers       = 4

# Enable threads for Sentry error submission
enable-threads = true

# Child processes do not need file descriptors
close-on-exec = true

# Avoid default 0000 umask
umask = 0022

# Run as weblate user
uid = site-user
gid = site-user

# Enable harakiri mode (kill requests after some time)
# harakiri = 3600
# harakiri-verbose = true

# Enable uWSGI stats server
# stats = :1717
# stats-http = true

# Do not log some errors caused by client disconnects
ignore-sigpipe = true
ignore-write-errors = true
disable-write-exception = true

Vhost Change

  1. Go to the Vhost of your site.

Below you see the default vhost. It’s forwarding all requests via reverse proxy to the App Port.
It’s useful for development where you use the built-in server, provided by most the Python Applications.

server {
  listen 80;
  listen [::]:80;
  listen 443 ssl http2;
  listen [::]:443 ssl http2;
  {{ssl_certificate_key}}
  {{ssl_certificate}}
  server_name www.domain.com;
  {{root}}

  {{nginx_access_log}}
  {{nginx_error_log}}

  if ($scheme != "https") {
    rewrite ^ https://$host$uri permanent;
  }

  location ~ /.well-known {
    auth_basic off;
    allow all;
  }

  {{settings}}

  index index.html;

  location /uwsgi {
    include uwsgi_params;
    uwsgi_read_timeout 3600;
    #uwsgi_pass unix:///run/uwsgi/app/weblate/socket;
    uwsgi_pass 127.0.0.1:{{app_port}};
  }

  location / {
    proxy_pass http://127.0.0.1:{{app_port}}/;
    proxy_http_version 1.1;
    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header X-Forwarded-Server $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header Host $http_host;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "Upgrade";
    proxy_pass_request_headers on;
    proxy_max_temp_file_size 0;
    proxy_connect_timeout 900;
    proxy_send_timeout 900;
    proxy_read_timeout 900;
    proxy_buffer_size 128k;
    proxy_buffers 4 256k;
    proxy_busy_buffers_size 256k;
    proxy_temp_file_write_size 256k;
  }

  location ~* ^.+\.(css|js|jpg|jpeg|gif|png|ico|gz|svg|svgz|ttf|otf|woff|woff2|eot|mp4|ogg|ogv|webm|webp|zip|swf)$ {
    add_header Access-Control-Allow-Origin "*";
    expires max;
    access_log on;
  }

  if (-f $request_filename) {
    break;
  }
}
  1. Replace the default vhost with the following one, replacing the location / to forward all requests to the uWSGI Service.

server_name

Replace the value of server_name with your domain.

server {
  listen 80;
  listen [::]:80;
  listen 443 ssl http2;
  listen [::]:443 ssl http2;
  {{ssl_certificate_key}}
  {{ssl_certificate}}
  server_name www.domain.com;
  {{root}}

  {{nginx_access_log}}
  {{nginx_error_log}}

  if ($scheme != "https") {
    rewrite ^ https://$host$uri permanent;
  }

  location ~ /.well-known {
    auth_basic off;
    allow all;
  }

  {{settings}}

  index index.html;

  location / {
    include uwsgi_params;
    uwsgi_read_timeout 3600;
    #uwsgi_pass unix:///run/uwsgi/app/weblate/socket;
    uwsgi_pass 127.0.0.1:{{app_port}};
  }

  #location ~* ^.+\.(css|js|jpg|jpeg|gif|png|ico|gz|svg|svgz|ttf|otf|woff|woff2|eot|mp4|ogg|ogv|webm|webp|zip|swf)$ {
  #  add_header Access-Control-Allow-Origin "*";
  #  expires max;
  #  access_log on;
  #}

  if (-f $request_filename) {
    break;
  }
}

Restart uwsgi Service

Restart the uwsgi service via systemctl to apply our configuration.

systemctl restart uwsgi

Service Check

To check if uwsgi is listening on our requested port e.g., 8090, we use netstat:

netstat -tulpn |grep uwsgi

If everything is correct, you should see an output like this:

tcp 16 0 127.0.0.1:8090 0.0.0.0:* LISTEN 8872/uwsgi

Reboot your instance to confirm that your application is working as expected.

Adding a Python Version

On this site, you will find a step-by-step guide on adding a custom Python Version to the instance.

We will add an older version Python 3.8, to our instance as an example.

Ubuntu

PPA Method

The first and easiest solution for Ubuntu users would be to import the deadsnakes team Launchpad PPA.
This will always contain the latest updates for Python and all extra packages that may be required.

  1. Install the prerequisite for adding custom PPAs:
apt update && apt -y install software-properties-common
  1. Add the deadsnakes/ppa to your APT package source list:
add-apt-repository ppa:deadsnakes/ppa -y
  1. Install Python 3.8:
apt update && apt -y install python3.8

Debian

Install Dependencies

First, we need to install the required dependencies to be able to build Python 3.8 from the source.

apt update && apt -y install build-essential zlib1g-dev libncurses5-dev libgdbm-dev libnss3-dev libssl-dev libreadline-dev libffi-dev libsqlite3-dev libbz2-dev

Compiling from Source

Python Releases

All Python Releases can be found on the following site: https://www.python.org/downloads/

  1. Download the source for Python 3.8.
wget https://www.python.org/ftp/python/3.8.13/Python-3.8.13.tgz
  1. Extract the downloaded tarball:
tar xf Python-3.8.13.tgz
  1. Configure with the flag –enable-optimizations:
cd Python-3.8.13
./configure --prefix=/usr --enable-optimizations
  1. Run make to start the compile process:
make
  1. Install Python 3.8 via altinstall, which maintains the default Python binary path in /usr/bin/python.
make altinstall
  1. Remove the source and the tgz:
rm -rf Python-3.8.13 Python-3.8.13.tgz
  1. Done! Python 3.8 has been installed and is now available to select in CloudPanel.

Remove Dependencies

apt --purge remove zlib1g-dev libncurses5-dev libgdbm-dev libnss3-dev libreadline-dev libffi-dev libsqlite3-dev libbz2-dev
  1. Copy files to the root directory of your site:
cp -R ~/tmp/joomla/* ~/htdocs/www.domain.com/
  1. Clean up the tmp directory.
rm -rf ~/tmp/*
  1. Create a Database, open your site in the browser, and go through the Joomla Installer.
  1. Done! Joomla is now installed.

Laravel

On this site, you find a guide to install Laravel on CloudPanel.

Creating a Laravel Site

Via CloudPanel

  1. Click on + Add Site and then click on Create a PHP Site.
  1. Select the Application Laravel 10, enter Domain Name, Site User, Site User Password, and click on Create.

Via CloudPanel CLI

If you like the command line, you can create a Laravel Site with the following command as root user.

clpctl site:add:php --domainName=www.domain.com --phpVersion=8.2 --vhostTemplate='Laravel 10' --siteUser='john-doe' --siteUserPassword='!secretPassword!'

Creating a Laravel Project

  1. Log in via SSH with the Site User:
ssh john-doe@server-ip-address
  1. Go to htdocs and delete the directory which CloudPanel has created:
cd htdocs && rm -rf www.domain.com
  1. Create a Laravel Project via Composer:
php8.2 /usr/local/bin/composer create-project --prefer-dist laravel/laravel:^10 -n www.domain.com
  1. Done! You can now open your site in your browser to see the welcome page.

WooCommerce

On this site, you find a guide to install WooCommerce on CloudPanel.

Creating a WordPress Site

Click on + Add Site and then click on Create a WordPress Site.

  1. Enter the Domain Name, Site Title, Admin User Name, Admin Password, and Admin E-Mail and click on Create.
  1. Login to the admin area of WordPress and click on Plugins –> Add New in the left menu and search for WooCommerce.
  1. Click on Install Now, Activate the Plugin, and go through the WooCommerce Installer.
  1. Done! WooCommerce has been installed.

Building a PHP Extension

On this site, you find a step-by-step guide on how to build a PHP Extension for a specific PHP Version.

  1. Make a backup of your instance.
  2. Install the dev package for the PHP Version.
apt update && apt -y install php8.1-dev
  1. Go to https://pecl.php.net/ and search for the extension like ssh2:
  1. Download the tgz via cURL:
curl -O https://pecl.php.net/get/ssh2-1.3.1.tgz
  1. Extract the tgz:
tar xf ssh2-1.3.1.tgz
  1. phpize the extension:
cd ssh2-1.3.1
phpize8.1
  1. Compile and build the extension:
./configure
make
make install
  1. Register the extension for CLI and FPM in the php.ini:
CLI
nano /etc/php/8.1/cli/php.ini
Add the following line at the end:
extension=ssh2.so
FPM
nano /etc/php/8.1/fpm/php.ini
Add the following line at the end:
extension=ssh2.so
  1. Restart the PHP-FPM service:
systemctl restart php8.1-fpm

Testing

After registering the PHP Extension, you can check if the extension is loaded for CLI and FPM correctly.

CLI

You can use grep to check if the extension is loaded:

php8.1 -m |grep 'ssh2'

If you don’t get an output, then the extension is NOT loaded.

FPM

To check if the extension is loaded for FPM, you can check the phpinfo.

  1. Create the file t.php in the root directory.

  2. Put the following content in the file:

<?php
phpinfo();
  1. Open t.php in your browser and search for the extension.

If you don’t find information about the extension, then it’s NOT loaded.

ionCube Loader

Attention

There is currently no ionCube Loader for PHP 8.0 available.

Using ionCube encoded and secured PHP files requires a file called the ionCube Loader to be installed on the web server and made available to PHP.

Enable Loader

The ionCube Loader extension is being shipped by default for all PHP Versions but disabled for performance reasons.

To enable ionCube Loader for a specific PHP Version, you need to enable it for CLI and FPM:

  1. Open the php.ini for the CLI and FPM:
nano /etc/php/8.1/cli/php.ini
nano /etc/php/8.1/fpm/php.ini
  1. Go to the end of the file and remove the semicolon in the beginning.
;zend_extension=ioncube_loader.so
  1. Restart the PHP-FPM service:
systemctl restart php8.1-fpm

Node.js for PHP Sites

On this site, you will find a guide on installing Node.js via the nvm (Node Version Manager) for your site.
  1. Log in via SSH with the Site User:
ssh john-doe@server-ip-address
  1. Install nvm with the following command:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.2/install.sh | bash
  1. Update the current shell environment:
source ~/.bashrc
  1. Install your required Node.js version e.g. 16:
nvm install 16
  1. Activate the installed Node.js version:
nvm use 16
  1. Done! Check the Node.js version:
node -v