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 "your_email@example.com";
- This creates a new SSH key, using the provided email as a label. Copy the private key to your Github secret variables. See this post on how to add your private key as an environment variable in Github.
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 user@remote-host
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 git@github.com: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 git@github.com: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/ssh-action@master
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 namedPRIVATE_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 thePRIVATE_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
- How to create a CI/CD pipeline with Github Actions
- How to manage infrastructure with Terraform
- Generating SSH keys for github
- How to set up ubuntu on a server
- Installing Docker on a server