Deploying a bare-bones Python webserver

16 Apr 2024 // programming

So you've prototyped a nifty Python webapp that serves a pre-built SPA client. You want to deploy it, but how?

Having done this a few times, I now have my shortest recipe to deploy a Python webapp to a bare-bones Ubuntu VM.

  1. Spin up the smallest shared VM (CPU-only) for $5/month from vendors such as linode or vultr (my current one). Choose Ubuntu for ease-of-install.

  2. Create a new super-user account. From your vendor, you'll get an IP.NUMBER and a root password. Ssh into your VM as root

     ssh root@IP.NUMBER
    

    Once in your VM, create the user account:

    adduser myusername 
    usermod -aG sudo myusername
    

    Restart the VM to allow sudo to propagate, and then log in as the new super-user.

     ssh myusername@IP.NUMBER
    
  3. At this point, you'll probably want to setup ssh-keys to simplify login and file copying by skipping passwords. If you've setup ~/.ssh/id_rsa.pub then on your local machine, you can send the keys to the VM:

    ssh-copy-id -i ~/.ssh/id_rsa.pub myusername@IP.NUMBER
    
  4. Copy your webserver over to the VM. You can use sftp, rsync, cyberduck or transmit.

  5. Once your files are transferred, a nice way to interact with your VM is through the Remote Explorer in Visual Studio Code. It allows editing and file management with an integrated terminal. The best feature is AUTOMATIC PORT MAPPINGS where VS Code will open your local webbrowser directly from the VM terminal with port mapping. But if you're happy with sftp and ssh, that's fine too.

  6. Ubuntu comes with python3 installed (Ubuntu23 -> python3.11) but missing a few crucial libraries. You'll want to install venv to manage a nice Python environment:

    sudo apt install python3.11-venv
    
  7. Hopefully your Python web-server is stored in a directory, say /home/myusername/webapp with either pyproject.toml or setup.py specifying all your dependencies and python tooling. Let's install them into a Python .venv sub-directory:

    python3 -m venv .venv 
    source .venv/bin/activate
    pip3 install -e .
    

    This will provide a new python3 that has access to the environment with all your depedencies installed. The location of this python3 is:

    /home/myusername/webapp/.venv/bin/python3
    
  8. Check that your webserver is working on the correct port (let's say 9023). If you're on VSCode, open the browser on the port directly and check the SPA client.

  9. Now let's set up a background task manager supervisord for your webapp where the logs will be written in the directory /var/log/webapp

    sudo apt install supervisor
    mkdir /var/log/webapp
    

    Create a supervsior task for our webapp /etc/supervisor/conf.d/webapp.conf. In our case the python command is cli.py in the ~/webapp/server directory:

    [program:webapp]
    directory=/home/myusername/webapp/server
    command=/home/myusername/webapp/.venv/bin/python3 cli.py run --port 9023
    autostart=true
    user=myusername
    autorestart=true
    stopasgroup=true
    killasgroup=true
    stderr_logfile=/var/log/webapp/errors
    stdout_logfile=/var/log/webapp/logs
    

    To make it easy to edit this file, let's chown it:

    sudo chown myusername /etc/supervisor/conf.d/webapp.conf
    

    Restart supervisor with our new webapp.conf job:

    sudo service supervisor restart
    sudo supervisorctl status webapp
    
  10. Install nginx to simply pass on all external http requests on port 80 directly to our port 9023 (or whatever port you want):

    sudo apt install nginx
    

    Open the default Ubuntu firewall for http access:

    sudo ufw allow 'Nginx HTTP'
    

    Create a server block for nginx /etc/nginx/sites-enabled/webapp that will accept data from any url on port 80 (http) and send the data to port 9023:

    server {
        listen 80;
        server_name ~^(.+)$;
        location / {
            proxy_pass http://0.0.0.0:9023;
        }
    }
    

    Take control of the file for easier editing:

    sudo chown myusername /etc/nginx/sites-enabled/fastapi
    

    Restart nginx to expose our webapp:

    sudo systemctl restart nginx
    
  11. Check your webapp on a browser from http://IP.NUMBER.