Welcome to TK's Web Quest, now fully independent! // Check out some photos of the fall colors from Guanella Pass in the blog! // Now built with 11ty! // Try our new Midnight flavor! // The Worst Webring: Additional slots now open! //
Welcome to TK's Web Quest, now fully independent! // Check out some photos of the fall colors from Guanella Pass in the blog! // Now built with 11ty! // Try our new Midnight flavor! // The Worst Webring: Additional slots now open! //
blog/2026-04-04-forejejo-deploy.md

blog entry

Forejo Deploy Activate!

Permalink for Forejo Deploy Activate!

simplifying deployment by complicating deployment

One of the things I lamented as I moved my site away from cloudflare pages was that there wasn't a good way for me to run site updates like I had been doing through Github Actions when TK-web first moved off of Pika nearly a year ago. One of the things Brendon over at Wavelengths has said a few times is that the process of making stuff for Wavelengths is reducing friction as much as humanly possible. Being able to take an idea from concept to published as fast as possible is a very important thing for him. My changes away from Pika and away from Github have both increased my friction. Instead of just going to a website and writing, I was suddenly spending more time with a draft "ready" before I could post it. This change is the first step back towards a smooth posting experience.

As of this morning, I now have a Forgejo Action that publishes my site anytime there's an update, just like how Github Actions worked before. Here's the workflow step-by-step.

  • I write a post in MarkText, my current markdown editor of choice (I use the Tkaixiang fork.)
  • I run the commands to push a commit to my TK-web repo on my self-hosted Forgejo instance.
    • Forgejo sees the publish.yaml in the .forgejo/workflows folder on commit and sends the action to Frank, my self-hosted Forgejo Runner.
      • Frank spins up the official Hugo Docker image and copies the repo to a working directory
      • Frank runs hugo -minify to generate a minified version of the site and stores the current date and time into a variable.
      • Frank sets up ssh for itself, importing SSH keys from Actions Secrets I have set up ahead of time.
      • Frank copies the ../public/ directory to my webserver over an ssh connection, setting the output name to the date and time variable it set earlier.
      • Finally, frank updates a symlink on the webserver to point at the newly copied directory.

The setup, as you can see, still requires the friction of sending a github commit, but, critically, my forgejo instance is available outside of my home network, so I can commit from anywhere! Let's look at the action:

name: deploy
on:
  push:
    branches:
      - main
  workflow_dispatch:

jobs:
  deploy:
    runs-on: docker
    container:
      image: ghcr.io/gohugoio/hugo:latest
    steps:
      - uses: actions/checkout@v4
        with:
          submodules: true
          fetch-depth: 0
      - name: get date
        id: date
        run: echo "::set-output name=date::$(date +'%Y%m%d%H%M%S')"
      - name: build site
        run: hugo --minify
      - name: setup ssh
        run: |
          mkdir -p ~/.ssh
          chmod 0700 ~/.ssh
          echo "${{ secrets.SSHKEY }}" >~/.ssh/id_rsa
          chmod 0600 ~/.ssh/id_rsa
          echo "{{ secrets.HOSTS }}" >> ~/.ssh/known_hosts
          chmod 0600 ~/.ssh/known_hosts
      - name: deploy site
        run: |
          scp -o StrictHostKeyChecking=no -o IdentitiesOnly=yes -i ~/.ssh/id_rsa -r public ${{ vars.USER }}@${{ vars.IP }}:/mnt/homeserver/applications/apache/${{ steps.date.outputs.date }}
      - name: update symlink
        run: |
          ssh -o StrictHostKeyChecking=no -o IdentitiesOnly=yes -i ~/.ssh/id_rsa ${{ vars.USER }}@${{ vars.IP }} "ln -sfn /mnt/homeserver/applications/apache/${{ steps.date.outputs.date }} /mnt/homeserver/applications/apache/public"

Not too complicated, once I got the SSH keys working. Setting up a limited user for the web server was a little tricky thanks to TrueNAS's gui-forward approach. Tim Howard's post was very helpful for me in getting this set up.

The next hole I'd like to fill in this setup is finding a good CMS or something that can handle the git commit for me. Especially if it's local only or self-hosted. If anyone has recommendations, please let me know!

About the author

TK

Writer, woodturner, photographer, podcaster, and game designer making cool things on the internet.