A half-full circle

Because I don't do social media.

Migrating from GitHub Actions to SourceHut Builds

Faster, more ethical, no AI, and EU-based.

After a couple of years running Gitea and Forgejo, I moved my private repos back to GitHub, because my instance was DDoS'd a couple of times and I found myself spending more time than I had wanted upgrading and maintaining it.

I didn't like it, and soon after they started pushing hard for Copilot and AI everywhere, I started looking for alternatives again. Codeberg became the obvious choice for mirroring public repos (I'm still in the process of doing this), but their terms don't allow private, non-FOSS projects, so I eventually chose SourceHut for those, and I've been a very happy customer for a few months, now!

I'd like to share how I migrated a couple of Github Actions for a Deno app with a docker compose based deploy via SSH from GitHub to SourceHut Builds, where I've got a ton more control (they're also faster and run closer to my infrastructure in EU)!

Hopefully this will help someone else with a similar problem.

Here's a sample .github/workflows/deploy.yml file I used to have:

name: Deploy
on:
  push:
    branches:
      - main
  workflow_dispatch:
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - name: Configure SSH
        run: |
          mkdir -p ~/.ssh/
          echo "$SSH_KEY" | tr -d '\r' > ~/.ssh/server.key
          chmod 600 ~/.ssh/server.key
          cat >>~/.ssh/config <<END
          Host server
            HostName host.example.com
            User root
            IdentityFile ~/.ssh/server.key
            StrictHostKeyChecking no
          END
          cat ~/.ssh/config
        env:
          SSH_KEY: ${{ secrets.SERVER_SSH_KEY }}
      - name: Deploy via SSH
        run: ssh server 'cd apps/example && git pull origin main && git remote prune origin && docker system prune -f && docker compose up -d --build && docker compose ps && docker compose logs'

And here's the new .builds/deploy.yml file:

submitter:
  git.sr.ht:
    enabled: true
    allow-refs:
      - refs/heads/main
secrets:
  - <SSH Key UUID>
image: alpine/latest
tasks:
  - configure_ssh: |
      cat >>~/.ssh/config <<END
      Host server
        HostName host.example.com
        User root
        IdentityFile ~/.ssh/<SSH Key UUID>
        StrictHostKeyChecking no
      END
      cat ~/.ssh/config
  - deploy_via_ssh: |
      ssh server 'cd apps/example && git pull origin main && git remote prune origin && docker system prune -f && docker compose up -d --build && docker compose ps && docker compose logs'

And the .github/workflows/test.yml file (make test calls deno test and a few other deno lint/format/check commands):

name: Run Tests
on: [push]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: denoland/setup-deno@v2
        with:
          deno-version-file: .dvmrc
      - run: |
          make test

And the respective new .builds/test.yml file:

submitter:
  git.sr.ht:
    enabled: true
    allow-refs:
      - refs/heads/main
secrets:
  - <SSH Key UUID>
image: ubuntu/lts
tasks:
  - test: |
      sudo apt-get update && sudo apt-get install -y curl unzip make
      curl -fsSL https://deno.land/install.sh | sh -s v$(cat repo-name/.dvmrc)
      export PATH="$HOME/.deno/bin:$PATH"
      cd repo-name
      make test

As you can see, they're mostly similar, but the subtle differences still took me some iterations to get the right Deno setup (which I do via .dvmrc), for example. The SSH Key is necessary on the second file in order to be able to check out the private repo. If it's public, you won't need that.

It's also possible there are improvements I can make to these, which I'm missing, so if you know of any, please let me know.

Thank you for your attention and kindness. I really appreciate it!