Streamlining Deployment with Github Actions

Streamlining Deployment with Github Actions

Deploying my site used to be straightforward. I have a VPS on Linode that I set up using Ansible. The workflow was simple: push to GitHub, SSH into the Linode server, pull the code from GitHub, and run the npm run build command.

This approach worked fine—until I started fixing typos frequently. The repetitive steps of pushing to GitHub, SSHing into the server, pulling updates, and running the build command quickly became tiresome.

I also realized that Hugo already provides a workflow to deploy directly to GitHub Pages when code is pushed to the main branch. So, I decided to automate the deployment process to my Linode VPS. Here’s how I refined it.


Version 1 - Build on VPS

The first step was to replicate my manual process: pull the code from GitHub and run the build command on the VPS. Here’s how the workflow looked:

  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Deploy to VPS
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.SERVER_NAME }}
          username: ${{ secrets.SSH_USERNAME }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}

          script: |
            cd ${{ secrets.PROJECT_PATH }}
            git pull origin main
            npm ci
            npm run build            

This setup worked, but it felt like overkill for a simple Hugo site. It’s more suited for complex build processes. For my use case, building the site on GitHub and copying the generated files to the VPS seemed more efficient. By doing so, I could avoid having tools like Go or npm installed on the VPS altogether.

Version 2 - Build on GitHub and copy site to VPS

To simplify the deployment process, I moved the build step to GitHub and updated the workflow to copy the generated site files to the VPS. Here’s the complete workflow:

name: Deploy Site

on:
  # Runs on pushes targeting the default branch
  push:
    branches: ["main"]

# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
  contents: read
  pages: write
  id-token: write

# Environment variables available to all jobs and steps in this workflow
env:
  HUGO_ENV: production
  HUGO_VERSION: "0.121.2"
  GO_VERSION: "1.20.5"
  NODE_VERSION: "20.0.0"
  TINA_CLIENT_ID: ${{ vars.TINA_CLIENT_ID }}
  TINA_TOKEN: ${{ vars.TINA_TOKEN }}

jobs:
  # Build job
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}

      - name: Install Hugo
        run: |
          curl -LO "https://github.com/gohugoio/hugo/releases/download/v${{ env.HUGO_VERSION }}/hugo_extended_${{ env.HUGO_VERSION }}_Linux-64bit.tar.gz"
          tar -xvf hugo_extended_${{ env.HUGO_VERSION }}_Linux-64bit.tar.gz
          sudo mv hugo /usr/local/bin/
          rm hugo_extended_${{ env.HUGO_VERSION }}_Linux-64bit.tar.gz
          hugo version          

      - name: Install Go
        run: |
          curl -LO "https://dl.google.com/go/go${{ env.GO_VERSION }}.linux-amd64.tar.gz"
          sudo tar -C /usr/local -xzf go${{ env.GO_VERSION }}.linux-amd64.tar.gz
          echo "export PATH=$PATH:/usr/local/go/bin" >> $GITHUB_ENV
          rm go${{ env.GO_VERSION }}.linux-amd64.tar.gz
          go version          

      - name: Setup Project
        run: npm run project-setup

      - name: Install npm dependencies
        run: npm install

      - name: Build site
        run: npm run build

      - name: Upload artifact
        uses: actions/upload-pages-artifact@v3
        with:
          path: ./public

      - name: Deploy to VPS
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.SERVER_NAME }}
          username: ${{ secrets.SSH_USERNAME }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          script: |
            # Remove existing files in the public directory
            rm -rf ${{ secrets.PROJECT_PATH_TEST }}/public/*

            # Exit the script if any command fails
            set -e            

      - name: Copy Built Files to VPS
        uses: appleboy/scp-action@master
        with:
          host: ${{ secrets.SERVER_NAME }}
          username: ${{ secrets.SSH_USERNAME }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          source: "./public/*"
          target: "${{ secrets.PROJECT_PATH }}/public"
          strip_components: 1

This workflow leverages GitHub Actions to handle the build process and deploys the final site files to the VPS, and it’s only a slight tweak from the Sample workflow provided in Hugo.

Setting Up SSH Keys

To enable GitHub to access your VPS, you’ll need to set up SSH keys:

  1. Generate an SSH key pair

    Run the following command on your local machine to create a new SSH key pair:

    ssh-keygen -t rsa -b 4096 -C "github_deploy_key"
    
  2. Add the public key to your server

    Copy the contents of the id_rsa.pub file and append it to the ~/ssh/authorized_keys file on your server:

    cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
    
  3. Add the private key to GitHub

    In the GitHub Repository, add a new secret named SSH_PRIVATE_KEY and paste the contents of the generated private key

Happy coding! 🚀

Feel free to use this Markdown content for your blog or website. If you have any more requests or questions, let me know!

comments powered by Disqus

Related Posts

Starting a Blog with Hugo and Adding a Contact Form

Starting a Blog with Hugo and Adding a Contact Form

Embarking on the journey of creating a personal blog or website often begins with the quest for simplicity and speed.

Read More
Fixing Automatic Route Parameter Mapping Deprecation in Symfony Using a Custom Rector Rule

Fixing Automatic Route Parameter Mapping Deprecation in Symfony Using a Custom Rector Rule

Symfony 7.1 added alot of new features and a few deprecations such as deprecating automatic mapping of route parameters into Doctrine entities.

Read More
Faster queues with Symfony and Go: When PHP isn't Fast Enough

Faster queues with Symfony and Go: When PHP isn't Fast Enough

For most applications PHP is more than capable, and I’ve never run into a situation where it has been too slow, especially as I usually process long-running tasks in the background with Symfony Messenger.

Read More