A black and white owl with a rainbow bowtie in line art style

SolarLabyrinth

Elden Lord that does software and game dev in their free time.

Godot Devs: You Need A Build System!

Published: 2025-04-06

If you’ve ever joined a game jam, it’s quite likely that you’ve had to upload your game to itch.io at least once. And if you’re like me, it’s far more than just once.

Many times have I exported my web build, forgotten to name files correctly, or uploaded an old zip file. And just when I fix those issues, I test my game, find a bug, and have to repeat the whole process over again.

Wouldn’t it be nice if the game was automatically exported and uploaded to itch every time I committed to main? GitHub can do this for us, and this guide will make setting it up easy.

If you already know how GitHub Actions work or just want to copy/paste the final result, feel free to skip ahead:

  1. How Do Manual Exports Work Anyway?
  2. GitHub Workflow Basics
  3. Setting Up Godot in GitHub Actions
  4. Setting Up Butler in GitHub Actions
  5. The Final Result
  6. Conclusion

How Do Manual Exports Work Anyway?

When I’m thinking about automating a bunch of manual steps, I like to list them all out so I don’t forget any.

Let’s start from the bare minimum, and imagine that we have a brand new computer - no software on it at all. And we need to configure it to create, export, and upload a game using Godot. What are all the actions we need to take to do this?

  1. First, we need to download Godot itself. We can get it from a few places such as Godot’s website or github releases page.

  2. Next, we need to actually create a game with the engine. It can be something quite simple for this example. You can use an existing project of yours if you like. I’m going to use my game PacSnek for this. Feel free to fork it and follow along if you don’t have a project readily available.

  3. Now we need to export our game as a web build. We can do this in the “Project > Export…” menu. This will prompt us to add or select a previously added preset. We’ll chose Web so we can upload it as a web build for our Jam.

  4. Since we’re on a brand new computer, Godot doesn’t have any export templates installed yet. Godot will prompt us to download them before we can proceed. These export templates change with every Godot release, and have to be installed every time you change versions.

  5. Once that’s done, we can choose an Export Path. I like to use a ./build directory in my repository for this. We need to create it if it doesn’t already exist. Itch also requires that web builds be named “index.html”. So I’ll select “build/index.html” for this value.

  6. Now we can finally export the project with the aptly named “Export Project…” button. This will create a bunch of files for us in our ./build directory.

  7. To upload this to itch we need to zip all of these files up into a .zip file. Making sure that index.html is at the root of that zip file. Select them all. Compress them into a .zip file and we can continue.

  8. Now we need to go to our Itch.io Creator Dashboard and create a new project for us to upload to. Set a title, url, and values for any of the other fields you would like to change. The most important values we care about for this guild is in the “Uploads” section. Upload your zip file there and make sure to check the play in browser button. You may need to modify some Embed options like the width and height, or add a full screen button.

  9. Hit save, navigate to the project’s page, and we should be done.

That’s a lot of steps. Lets automate each and every one with a GitHub Actions.

GitHub Workflow Basics

To get GitHub to do things for us, we need to create what’s called a “workflow” file. These are .yml files located in a .github/workflows directory in our project. We’ll create one for this guide and call it build.yml. It could be named anything, but this seems descriptive to me.

Copy and paste this into that build.yml file:

# .github/workflows/build.yml

name: Build

on:
  push:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Repository
        uses: actions/checkout@v4

      - name: Say Hi
        run: echo "Hello World!"

Go ahead and save that file, commit, and push it up to GitHub. Now check out the “Actions” tab in your repository’s page.

You should see a workflow running. Clicking on it will bring you to the overview page for that run. It will list all the jobs on the left. Selecting the build job here will show you all actions that github is performing during this workflow.

You should see a “Say Hi” step in that list. Expanding it should show that it printed “Hello World!” to the console.

Let’s explain what each of these entries in the .yml files does before moving on.

name: Build

This is the name of our workflow. We can call it anything we like. That name is what will appear in GitHub’s UI.

on:
  push:

These lines describe what events can trigger our workflow. There’s a lot of events to choose from, but this option will run it on every push to our repository.

Maybe we only want to run it on pushes to our main branch, but not any other branch. For this we can say:

on:
  push:
    branches:
      - main

Finally we have the jobs section:

jobs:
  build: # <- We can name this job anything
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Repository # <- Step names can be any value you like too
        uses: actions/checkout@v4

      - name: Say Hi
        run: echo "Hello World!"

This lets us define a list of jobs we want to run in this workflow. Ours has 1 job called “build”. We could name this anything we like, but that seems descriptive for what we want to do.

This build job states that it runs on “ubuntu-latest”. This will cause GitHub to provision a brand new linux machine every time this workflow runs to run the steps we specify.

GitHub also supports Windows & MacOS runners here, but Linux is usually simpler and faster for automation such as this.

The steps section tells github what commands to run:

steps:
  - name: Checkout Repository
    uses: actions/checkout@v4

This is where GitHub gets interesting. Whenever you see a step that has a “uses” key, that means you’re wanting to use a “Custom Action”. These are collections of scripts or code that other people create and make available for you to use.

The value “actions/checkout@v4” is shorthand for a GitHub repository link: https://github.com/actions/checkout. The version number refers to one of it’s releases.

This action is used to clone your repository to the newly created Linux runner. It’s created by GitHub itself, but there are tons of custom actions you can choose from in the GitHub marketplace. You can even make your own, either in your projects repository, or in a separate repo you create just for that action.

Our second step is much more simple:

- name: Say Hi
  run: echo "Hello World!"

Whenever you see the “run” key, that means you’re wanting to run this bash command in your Linux machine’s terminal. In this case, we’re just telling it to print the string: “Hello World!”

We can use any command we like here. We can install packages with apt, download files with wget, ssh to remote machines to do something, or even export and upload our Godot Games to Itch.io.

Lets finally get started with doing that.

Setting Up Godot in GitHub Actions

An easy way to download godot is from their GitHub releases page: https://github.com/godotengine/godot-builds/releases

At the time of writing, 4.4.1 is the latest stable release of godot, but you can choose whatever version you are using. These commands should be valid for any version of Godot 4 you substitute in. Godot 3 is a bit different and may require more investigation.

Since our GitHub machine is using Linux, we’ll want to download the linux.x86_64 version of Godot. And since we want to export web builds, we’ll chose the non-mono version.

We can use the wget command to download files on Linux. Our workflow step would look like this:

- name: Download Godot
  run: wget https://github.com/godotengine/godot-builds/releases/download/4.4.1-stable/Godot_v4.4.1-stable_linux.x86_64.zip

Godot provides their releases as .zip files, so after we download it, we need another step to unzip it. Linux has a helpful unzip command that does this:

- name: Unzip Godot
  run: unzip Godot_v4.4.1-stable_linux.x86_64.zip

This leaves us with a really ugly file name though: Godot_v4.4.1-stable_linux.x86_64. We can rename it to something prettier like godot with this command:

- name: Rename Godot
  run: mv Godot_v4.4.1-stable_linux.x86_64 godot

While we’re here. Lets also install the Export Templates. We can get these from the same releases page as the Godot Executable. Make sure to chose the non-mono version, since that’s the one we’re using.

- name: Download Export Templates
  run: wget https://github.com/godotengine/godot-builds/releases/download/4.4.1-stable/Godot_v4.4.1-stable_export_templates.tpz

These are provided as a .tpz file, which is just another kind of compressed archive. unzip can handle this just fine also.

- name: Unzip Export Templates
  run: unzip Godot_v4.4.1-stable_export_templates.tpz

This will drop the export templates in the root of your project’s repository, but that’s not where Godot looks for them by default when exporting your projects. We’ll need to move them to the right place.

On linux, Godot expects the export templates to be located at ~/.local/share/godot/export_templates/4.4.1.stable. So that’s where we’ll move them to. These commands will do this for us. First we create the export template directory if it doesn’t exist, then we move the templates there.

- name: Create Export Template Directory
  run: mkdir -p ~/.local/share/godot/export_templates/4.4.1.stable

- name: Move Export Templates
  run: mv templates/* ~/.local/share/godot/export_templates/4.4.1.stable/

Now that we have Godot and our Export Templates installed, we can actually export our game:

- name: Export Game
  run: |
    mkdir -p ./build
    godot --headless --export-release "Web" ./build/index.html

This does a lot of things:

Note:

You need to set the “Web” export preset up in the Godot GUI before this command will work. If the export_presets.cfg file is being ignored in your .gitignore file, then you’ll need to remove that and check it into your repo. Otherwise, GitHub Actions will not see your preset.

Putting it all together: we should have something that looks like this:

# .github/workflows/build.yml

name: Build

on:
  push:
    branches:
      - main

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Repository
        uses: actions/checkout@v4

      - name: Download Godot
        run: wget https://github.com/godotengine/godot-builds/releases/download/4.4.1-stable/Godot_v4.4.1-stable_linux.x86_64.zip

      - name: Unzip Godot
        run: unzip Godot_v4.4.1-stable_linux.x86_64.zip

      - name: Rename Godot
        run: mv Godot_v4.4.1-stable_linux.x86_64 godot

      - name: Download Export Templates
        run: wget https://github.com/godotengine/godot-builds/releases/download/4.4.1-stable/Godot_v4.4.1-stable_export_templates.tpz

      - name: Unzip Export Templates
        run: unzip Godot_v4.4.1-stable_export_templates.tpz

      - name: Create Export Template Directory
        run: mkdir -p ~/.local/share/godot/export_templates/4.4.1.stable

      - name: Move Export Templates
        run: mv templates/* ~/.local/share/godot/export_templates/4.4.1.stable/

      - name: Export Game
        run: |
          mkdir -p ./build
          ./godot --headless --export-release "Web" ./build/index.html

This works, but it’s a lot of steps to have to copy and paste. And you may have noticed that we’re hard coding the godot version all over the place. If we ever wanted to upgrade it would be nice to just have one place to change it.

This is where custom actions really shine. I’ve created a custom action that does all of the Godot setup for you in a single step. You can use it directly here, or copy the action.yml directly from it’s repository.

- name: Setup Godot
  uses: solarlabyrinth/action-setup-godot@v2
  with:
    version: 4.4.1-stable

The “with” key lets you pass variables into a custom action. Choose whichever version of godot you would like. This action is tested against all stable versions of Godot 4 and the latest unstable release. Additional variables are detailed in the Readme.

This action also adds godot to the system path, so we don’t need to add a ./ in front of godot when we use it in the “Export Game” step.

Note:

Always be cautious when using custom actions from unknown sources. Especially if you’re passing credentials to them. This action is simple and easily audited, but you might want to consider copying the action.yml file directly into your repository to avoid future supply-chain attacks.

Our workflow file now looks like this:

# .github/workflows/build.yml

name: Build

on:
  push:
    branches:
      - main

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Repository
        uses: actions/checkout@v4

      - name: Setup Godot
        uses: solarlabyrinth/action-setup-godot@v2
        with:
          version: 4.4.1-stable

      - name: Export Game
        run: |
          mkdir -p ./build
          godot --headless --export-release "Web" ./build/index.html

Much nicer, if I do say so myself.

Setting Up Butler in GitHub Actions

Butler is a handy command line tool that Itch provides to upload games to their site. Setting it up is very simple. Their docs cover it in detail.

This is a workflow step that will install butler for us.

- name: Download Butler
  run: |
    wget -O butler.zip https://broth.itch.ovh/butler/linux-amd64/LATEST/archive/default
    unzip butler.zip
    chmod +x butler
    ./butler -V

This downloads the latest butler .zip file, unzips it, marks it as executable, and then prints the butler version to the console.

Now, we need to provide our GitHub machine with credentials to publish games to our Itch account.

Go to Itch’s API key page and click “Generate new API key”.

View the full key, and copy it to your clipboard.

Now we need to save this key in your GitHub Repository’s Secrets list.

Go to your game’s repository settings page and navigate to the “Secrets and variables > Actions” page.

Create a new repository secret.

GitHub will now encrypt this api key and provide it to our workflow whenever it runs. Lets use it.

On Linux, Butler expects our api key to live in a file called butler_creds in the ~/.config/itch directory. So lets make a workflow step that copies our API key into that location.

- name: Save Butler API Key
  run: |
    mkdir -p ~/.config/itch
    echo "${{ secrets.BUTLER_API_KEY }}" > ~/.config/itch/butler_creds

If you’re wondering if I have a nice 1-step action for you to use for this also. The answer is yes, absolutely.

- name: Setup Butler
  uses: solarlabyrinth/action-setup-butler@v1
  with:
    key: ${{ secrets.BUTLER_API_KEY }}

Now that we have butler installed and authenticated with our Itch account, we can use it to push our game to our Itch page. If you haven’t already created a page for your game, do this now.

- name: Upload to Itch
  run: butler push ./build ITCH_ACCOUNT/GAME_NAME:web

This calls butler with some arguments. You’ll need to customize it for your account and game. If you’re not using the custom action, you’ll need to add a ./ in front of the butler command: ./butler push ./build ITCH_ACCOUNT/GAME_NAME:web. Since we’re not calling butler from the system path.

And that’s it!

The Final Result

Our final file should look like this. Committing it to your game’s repository will cause it to run automatically every time you commit to main.

Make sure you have:

  1. Saved the BUTLER_API_KEY as a repository secret.
  2. Replaced the ITCH_ACCOUNT and GAME_NAME placeholders in the build.yml file with your own values.
  3. Set up the “Web” Export Preset in the Godot GUI, and checked export_presets.cfg into your repository.

The first time a web build is published through Butler, you will need to go to the Itch Project’s Settings Page and set the “Kind of Project” to an “HTML” game. Then also mark the web.zip under Uploads as play in browser. Every build after the first will remember this value.

Final Workflow File

# .github/workflows/build.yml

name: Build

on:
  push:
    branches:
      - main

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Repository
        uses: actions/checkout@v4

      - name: Setup Godot
        uses: solarlabyrinth/action-setup-godot@v2
        with:
          version: 4.4.1-stable

      - name: Export Game
        run: |
          mkdir -p ./build
          godot --headless --export-release "Web" ./build/index.html

      - name: Setup Butler
        uses: solarlabyrinth/action-setup-butler@v1
        with:
          key: ${{ secrets.BUTLER_API_KEY }}

      - name: Upload to Itch
        run: butler push ./build ITCH_ACCOUNT/GAME_NAME:web

Repository Secrets

NameValue
BUTLER_API_KEYCopy and pasted from your Itch.io API key page

Conclusion

That’s it! Congratulations. Go forth and enjoy not having to waste your precious Game Jam time on deployment shenanigans.

Automated deployment tools like GitHub Actions are really powerful and convenient for all kinds of tasks. You can run tests every time you change files, deploy dedicated server builds to your dev servers, or automate building client versions for several platforms all in one go.

The only limits when it comes to build automation is your creativity. Well… and time… and I guess API availability. And possibly free tier usage limits. And… well. Ok. You get the point.

Think about automating whatever frustrates you. Automating it once means you never have to mess with it again.