Deploy Lume with GitHub Actions

Until now, I updated my website manually by sending static website assets to my FTP server via Cyberduck. This approach is ok, but come on, I am a techie—there has to be a better way.

When I initially created my site, I wanted to use the American provider Netlify who automatically builds and deploys sites based on a Git repository. But due to GDPR and Schrems 2, I wanted to be on the safe side when I chose my host. I live in Germany, so I decided to pick a German hoster (Hetzner).

Unfortunately, Hetzner’s interface and deployment process are miles away from modern hosting solutions like Netlify—whenever I access the backend, it feels like opening a portal to the early 90s.

I wanted to build a custom workflow that is similar to Netlify. I wanted a system that automatically builds the site when I send updates to my Git repository and deploys it to Hetzner via FTP (FTPS to be specific).

I quickly discovered GitHub Actions that allow running custom tasks when a GitHub repository is updated. As far as I understand GitHub Actions consist of two parts. First, I can specify when a GitHub action runs. Second, it allows me to execute some tasks.

They are written in yaml file format. Something I know very well from the templates used by the Lume static site generator. So nothing to hold me back.

The Actions need to be saved in a special folder of the repository .github > workflows > {action}.yaml. No lengthy configuration process, just static files inside my repository. Yay.


I want to trigger the Action when I push commits to my GitHub repository that holds the template and content of my site. The GitHub Action documentation has many triggers available—push solves my use case.

Then I realised that this would deploy my site even on the smallest update. What a waste of energy. After some Google-Fu I found that I can add further constraints to the trigger. I decided that I only want to build the site when my commit contains a tag. So the first part of my Action yaml looks like this:

name: Deploy Site
    - '*'

During development, I used a different trigger workflow_dispatch that allows to manually trigger the Action in the GitHub Action web interface. At this state my yaml looked like this:

name: Deploy Site
on: workflow_dispatch

This was a good way to avoid the need to push updates to trigger the Action until I solved the actual tasks.


The second part of the GitHub Action yaml contains the actual steps I need to perform to deploy the site. When a GitHub Action is triggered it basically sets up a new server and runs the specified commands. Most of these tasks have already be solved. GitHub Actions has hundreds of packages that are shared open-source on GitHub to solve common tasks. So instead of writing a custom script to send my files to the FTP server I can simply load and configure an available FTP Action.

For my use case I have the following tasks:

  1. I need to set up the server.
  2. I need to check out the files of my Git repository to load them on the server.
  3. I need to download and install Deno—the JavaScript runtime used by Lume.
  4. I need to download and run the Lume generator to build the static assets of my website.
  5. I need to push the built files to my FTP server.

Each step usually consists of a name and an external Action or run command. The uses command loads an external Action—the with property can be used for configuration. The run command allows running a shell command on the server for simple tasks that do not require a script.

The tasks (or steps) of my Action yaml look like this. I added the numbers of the above tasks for reference:

    runs-on: ubuntu-latest # 1
      - name: Checkout Git Repository # 2
        uses: actions/checkout@v2
      - name: Setup Deno # 3
        uses: denoland/setup-deno@v1
          deno-version: v1.x
      - name: Build site with Lume # 4
        run: deno run -A
      - name: Deploy to FTP # 5
        uses: SamKirkland/FTP-Deploy-Action@4.2.0
          server: ${{ secrets.FTP_SERVER }}
          protocol: ftps
          port: ${{ secrets.FTP_PORT }}
          username: ${{ secrets.FTP_USER }}
          password: ${{ secrets.FTP_PASSWORD }}
          local-dir: ./_site/

One thing to be very careful when working with Actions is that the yaml file is available publicly. This means any password is displayed in plain text to the entire world. Luckily GitHub offers Secrets that can be used to hide such details from the public. E.g. I put the FTP Username, URL, Port and Password in a GitHub Secret. Then they can be accessed via the secrets property.

That is it. The setup was quite fast and took maybe 30 minutes after I figured out the details (e.g. how I can select specific commits via tags and what external Actions I can depend on). You can find the full yaml on my GitHub repository. I hope this inspires you to start automating your work on GitHub.