How to deploy a Python app on a VPS - Part II

How to deploy a Python app on a VPS - Part II

December 16, 2024

Image created by Midjourney


In the last post we created a sudo user and updated our packages. In this post we're gonna go over how to set up Docker and GitHub Actions


Install a self-hosted GitHub Runner

You'll need a "runner" in your VPS that listens to git push-es to your repository. It's basically a webhook endpoint that GitHub pings when new changes are pushed to your repo. Every time you merge to master/main, GitHub will ping this runner and it will perform some actions that you define in a config file (pull Docker images from a registry, create containers etc). To find instructions to install a runner, go to your GitHub repo and click on the Settings tab, and then click on "Runners" under the Actions section. There should be a button to create a runner which should reveal instructions that look something like this. DON'T COPY AND PASTE THESE COMMANDS. COPY THE ONES IN YOUR GITHUB

  
$ mkdir actions-runner && cd actions-runner
$ curl -o actions-runner-linux-x64-2.319.1.tar.gz -L https://github.com/actions/runner/releases/download/v2.319.1/actions-runner-linux-x64-2.319.1.tar.gz
$ echo "3f6efb7488a183e291fc2c62876e14c9ee732864173734facc85a1bfb1744464  actions-runner-linux-x64-2.319.1.tar.gz" | shasum -a 256 -c
$ tar xzf ./actions-runner-linux-x64-2.319.1.tar.gz
  

Yours would look different, but the idea is the same. Let's go over each line and understand what's going on

  
$ mkdir actions-runner && cd actions-runner
  

Pretty simple, we're just creating a directory to keep all our runner files. We then go to this directory because that's where we'll run the next few commands

  
$ curl -o actions-runner-linux-x64-2.319.1.tar.gz -L https://github.com/actions/runner/releases/download/v2.319.1/actions-runner-linux-x64-2.319.1.tar.gz
  

This fetches a tar file from GitHub that contains the runner installer

  
$ echo "3f6efb7488a183e291fc2c62876e14c9ee732864173734facc85a1bfb1744464  actions-runner-linux-x64-2.319.1.tar.gz" | shasum -a 256 -c
  

This step is optional, but recommended. We verify the hash of this file that we just downloaded to make sure it comes from a legit source (GitHub)

  
$ tar xzf ./actions-runner-linux-x64-2.319.1.tar.gz
  

This extracts the tarball so we can run the install scripts


Now run the GitHub runner and see if everything works

  
# Create the runner and start the configuration experience
$ ./config.sh --url https://github.com/prithajnath/portfolio --token AB6577LXKEYPRWSW2PPCS63GZVI7M# Last step, run it!
$ ./run.sh
  

Add the GitHub Runner as a daemon

If everything works as expected, go ahead and the runner as a systemd service. The GitHub runner instructions don't mention this but this is really important. Systemd is how you get the operating system to handle background processes for you (You can copy these commands in this section and following sections)

 
$ sudo ./svc.sh install 
$ sudo ./svc.sh start
    
You can check on the status of the runner using the status command. This is a full fledged system service so you can use your typical systemd commands as well
 
$ sudo ./svc.sh status 
    
This is what the status logs should look like
      
        ● actions.runner.prithajnath-portfolio.self-hosted.service - GitHub Actions Runner (prithajnath-portfolio.self-hosted)
     Loaded: loaded (/etc/systemd/system/actions.runner.prithajnath-portfolio.self-hosted.service; enabled; preset: enabled)
     Active: active (running) since Sun 2024-12-15 23:02:29 UTC; 16h ago
   Main PID: 2884587 (runsvc.sh)
      Tasks: 20 (limit: 1113)
     Memory: 107.4M (peak: 306.4M)
        CPU: 46.581s
     CGroup: /system.slice/actions.runner.prithajnath-portfolio.self-hosted.service
             ├─2884587 /bin/bash /home/prithaj/actions-runner/runsvc.sh
             ├─2884590 ./externals/node20/bin/node ./bin/RunnerService.js
             └─2884597 /home/prithaj/actions-runner/bin/Runner.Listener run --startuptype service

    Dec 15 23:02:31 prithaj runsvc.sh[2884590]: Current runner version: '2.321.0'
    Dec 15 23:02:31 prithaj runsvc.sh[2884590]: 2024-12-15 23:02:31Z: Listening for Jobs
    Dec 15 23:02:32 prithaj runsvc.sh[2884590]: 2024-12-15 23:02:32Z: Running job: build_container_image
    Dec 15 23:04:15 prithaj runsvc.sh[2884590]: 2024-12-15 23:04:15Z: Job build_container_image completed with result: Succeeded
    Dec 15 23:04:17 prithaj runsvc.sh[2884590]: 2024-12-15 23:04:17Z: Running job: deploy_all_services
    Dec 15 23:04:34 prithaj runsvc.sh[2884590]: 2024-12-15 23:04:34Z: Job deploy_all_services completed with result: Succeeded
    Dec 15 23:50:22 prithaj runsvc.sh[2884590]: 2024-12-15 23:50:22Z: Running job: build_container_image
    Dec 15 23:51:58 prithaj runsvc.sh[2884590]: 2024-12-15 23:51:58Z: Job build_container_image completed with result: Succeeded
    Dec 15 23:52:00 prithaj runsvc.sh[2884590]: 2024-12-15 23:52:00Z: Running job: deploy_all_services
    Dec 15 23:52:19 prithaj runsvc.sh[2884590]: 2024-12-15 23:52:19Z: Job deploy_all_services completed with result: Succeeded

      
    

Install Docker

Now that we have a GitHub runner up and running, it's time to install Docker. Docker is a widely used containerization tool and it comes in two parts.

  • 1. The Docker daemon
  • 2. The Docker CLI

You can find the latest instructions to install the Docker daemon here, but I have copied and pasted the two most important snippets here

        
# Add Docker's official GPG key:
sudo apt-get update
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

# Add the repository to Apt sources:
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
        
      
And then the actual installation.
        
$ sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
        
      

Why do we need Docker?

Docker lets you package your application into little containers so you can run them anywhere (with an asterisk). Your host machine, like your DigitalOcean droplet, Raspberry pi etc may not have all the dependencies you need to run your app. You may say that this can simply be solved by a requirement.txt or a Pipefile.lock, and you would be mostly correct, except for when you need C libraries that some Python packages rely on (like Pandas or Numpy).


How does Docker fit into the workflow?

The GitHub runner listens for pushes to your GitHub repo. Every time you push to main/master, the GitHub runner will use the Docker CLI to tell the Docker daemon to start building new Docker images, push them to a registry and deploy new containers from those images


Post Docker Installation

After installing Docker, you need to add your user to the docker group in order to be able to run Docker commands without using sudo all the time. Make sure you log out and SSH back into the droplet after running this command.

      
$ sudo usermod -aG docker $new_user
      
    

Remember, $new_user is the user you set in the last article. If everything goes well, you should be able to run docker ps in the terminal and see the following output

      
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES
      
    

This confirms that the Docker daemon is running and you can use the Docker CLI without sudo.


Next steps

This was a long one. Next up we're gonna learn how to write a Dockerfile and some YAML to trigger our Github Actions Runner