How to deploy your Python app to a Virtual Private Server (VPS) using Github actions

In this short tutorial, we will be learning how to automate deployment to a VPS via SSH using Github actions and a simple Python script.

LEVEL - 💻 💻 Intermediate - Advanced

Prerequisites

To get started you will need the following.

  • Linux VPS with Docker and Git installed - You can create a DigitalOcean droplet with Docker and git already installed here.
  • Dockerized Python or other application 
  • Knowledge of Docker, Github actions and SSH

Step 1 - Setting up your server for the first time

Our goal is to automatically deploy the latest version of an application to a server each time we push to a certain branch. To do that we need to first prepare our server for our application. You should have your server set up and ready to go.

1.1 Create SSH Keys and add them to Github

The workflow will run commands on a remote server via SSH. We need to create an SSH key and add it to the list of allowed keys on the server.

We prefer to generate fresh SSH keys for each of our servers. Generating the SSH key can be done on your local machine.

On your local machine, generate an SSH key

$ ssh-keygen -t ed25519 -C "[email protected]";

Copy the private key to your clipboard and add it as an environment variable in Github.

$ pbcopy < ~/.ssh/id_ed25519

1.2 Add your public key to the server

1. Copy the public key to the server using ssh-copy-id

$ ssh-copy-id -i ~/.ssh/id_ed25519.pub [email protected]

2. Manually copy the public key to the server

>> copy keys to clipboard
$ pbcopy < ~/.ssh/id_ed25519.pub
or 
$ pbcopy < ~/.ssh/id_rsa.pub 

>> ssh into server and use text editor to paste contents in authorized_keys
$ nano ~/.ssh/authorized_keys

If you are not familiar with how to generate SSH keys we suggest you read this post from Github.

1.3 Clone repository to server and check your app is working.

This section is out of the scope of this article but has been included for completeness.

Clone the repository to the server and set up the app is working as expected. For example, if we were cloning Advantch's sample fastapi project we would run the following.

$ mkdir demo
$ git clone [email protected]:advantch/fastapi-starter-template.git ./demo
$ cd fastapi-starter-template && docker-compose build
$ ufw allow 80
$ docker-compose up -d
  • mkdir core - create a directory called demo to dump the contents of the repository into.
  • git clone [email protected]:advantch/fastapi-starter-template.git ./demo - clone the repo into the folder and build the stack
  • ufw allow 80 - expose port 80 to the outside world
  • docker-compose up -d - launch docker-compose in detached mode.

Step 2 - Automating deployment scripts

In this first part, we will add a script to automate deployment tasks using Invoke. Invoke is a Python task execution tool & library. This is a personal preference. You could also use a bash script in place of this.

Create a file called tasks.py in the root directory of your project.

from invoke import task

SERVER_APP_FOLDER = '/home/apps/demo'


@task
def deploy_app_to_server(c, docs=False, bytecode=False, extra=''):
    """
    Pulls latest branch, rebuilds containers and runs migration command
    """
    with c.cd(SERVER_APP_FOLDER):
        c.run("echo 'Pulling latest commit and building containers'")
        c.run("git pull")
        c.run("docker-compose -f production.yml build")

        c.run("echo 'Containers successfully build launching app'")
        c.run("docker-compose -f production.yml run --rm django python manage.py migrate")
        c.run("docker-compose -f production.yml up -d")
  • SERVER_APP_FOLDER = '/home/apps/demo' - this is the name of the app folder for the application. Rename this appropriately.
  • @task decorator marks the function as a task that can be run by Invoke.
  • c.run("git pull") - will pull the default branch from the server. This assumes you have already set up the server the first time as shown in step one.

Step 3 - Add a workflow to deploy your application to the server

Add the following workflow to .github/workflows/deploy.yaml.

name: Deploy to demo app

on:
  push:
    branches:
      - main_demo

jobs:
  deploy-demo-app:
    runs-on: ubuntu-latest
    steps:
      - name: Deploy to server
        uses: fifsky/[email protected]
        with:
          host: 'demo.advantch.com'
          user: 'apps'
          key: ${{ secrets.PRIVATE_KEY }}
          script: cd your-app-folder && python3 -m invoke deploy-demo-app-to-server

This workflow will run the job deploy-demo-app on every push to the main_demo branch.

  • host: 'demo.advantch.com' - replace this with your host (IP or domain).
  • user: 'apps' - set the SSH user name. e.g. 'apps'
  • key - defines the private key to use for login
  • script - defines the script which will run on the server.

Step 4 - Test your workflow locally and deploy

To get fast feedback, we can use the excellent act library to test workflows locally before deploying them to GitHub.

Head over to the GitHub page and follow the instructions on how to install act.

Once that is done, test the workflow to make sure everything is working.

Run act -l to list available workflows.

$ act -l 

>> 
ID               Stage  Name             
deploy-demo-app  0      deploy-demo-app 

Test the workflow


$ export PRIVATE_KEY=$(cat ~/.ssh/id_ed25519)
$ act -j deploy-demo-app -s PRIVATE_KEY

>>

[Deploy to demo app/deploy-demo-app] 🚀  Start image=catthehacker/ubuntu:act-latest
[Deploy to demo app/deploy-demo-app]   🐳  docker run image=catthehacker/...
...
... 
| To delete this message of the day: rm -rf /etc/update-motd.d/99-one-click
[Deploy to demo app/deploy-demo-app]   ✅  Success - Deploy to server 
  • export PRIVATE_KEY=$(cat ~/.ssh/id_ed25519) - creates an environment variable named PRIVATE_KEY and sets it to the private key generated earlier.
  • act -j deploy-demo-app -s PRIVATE_KEY - run the job deploy-demo-app passing in the PRIVATE_KEY.

If everything went smoothly, you can commit and push to your remote repo on GitHub. The action will deploy the latest app to the server whenever you push to the main branch.

Conclusion

GitHub Actions in combination with automation scripts are powerful tools that you can use to automate all your software workflows and simplify your DevOps. We have included some additional reading materials below for areas we did not cover in detail.

Additional reading material



Copyright © 2022 www.advantch.com