Let's Play recording by 100th_coin of Clique Hell High, which was released 112 times
Shipping software is stressful and getting your game delivered and playable during the zero hour of Ludum Dare is even more stressful. There are so many places where the delivery process can go wrong and doing it by hand makes that process fragile. With only 48-72 hours to make a game, every time 5-20 minutes is spent packaging up a game and deploying to itch.io is taking precious time away from development of a game. However, the feedback is critical throughout the development process, so what is one to do? Well, we shipped Clique Hell High 112 times since Friday afternoon, and none of us had to step away from any of our tasks at hand. How did we do it? We used Github Actions to build an automated continuous delivery pipeline: every time we tagged a release, automated tooling exported our game, packaged it up, and uploaded it to itch.io. Here are the details on how we did it.
Tools used:

The first step in the pipeline was our actual IDE/Engine. Looking over the landscape of game engines, Godot was the best featured with all the tooling required to set it up in a CICD pipeline. If you haven't used Godot, give it a look, because while scenes inside scenes is the killer app of Godot, overall it's a great game engine/editor for small projects like game jam. It's the perfect engine for small game projects.
Godot lets you place scenes inside scenes as nodes, and if your team agrees to only one team member editing a scene at a time, you still have a lot of opportunities to synergize and work on the same systems together. One person works on arranging the level in one scene, and is the only person who's allowed to make changes in Git for it. How do the rest of the team members contribute to the level simultaneously? With scenes as instances. Towards the end of the project we would have one team member working on the mechanics for the exit door in its own scene, while another arranged the enemies in the main map. Nobody had merge conflicts thanks to scenes within scenes in godot.
112 git versions, each one published automatically to itch.io
During the first hour, we had a rough level that the player could wander in and the rest of our team who weren't developers were able to play it on itch.io, with each iteration released hourly. Practically with every change of our code base, our pipeline shipped a new version to itch.io.

The next component was our version control system and merging our work together, integrating it, as frequently as possible, multiple times per hour. To do this, we used git, backed by github. Every team member actively working with the Godot editor had a copy of Github Desktop, and a github account with access to the private project repository. Every hour, each contributor committed their work to github and merged it together. This meant everybody who was dependant on everyone else got to see if their work stepped on the toes of others right away. Our play testers on the team could spot problems with integrated systems quickly.
Github Desktop makes it possible to snapshot your work and integrate with other contributors on the team
Compared to a network drive, a version control system like git lets our team snapshot their local changes (undo on steroids) while integrating together upstream and test their integrated changes together.
Delivery pipelines have been around for a while but they used to be the domain of large corporations: clunky jenkins servers packaging up code bases maintained by hundreds of developers, with the automated systems being babied like precious, cantankerous animals. Over the past decade, these automated systems have become easier to set up with bot Bitbucket and Github offering completely free, easy to use solutions.
export stage of our pipeline
Now that we have our code integrated, how did we package it up? The solution was Github Actions, particularly the firebelly/godot-export github action. With a .github/workflows/main.yml file using the documented YAML code from the export action, every time we tagged a release, the github action packaged the godot export up into zip files. Instead of requiring a team member to manually export the project to their local computer, the pipeline did this for us. You don't even have to use github, because other providers offer similar solutions, all of them with free tiers:
Add an action for exporting the game in your pipeline. Here's a snippet of the yaml we used to export our Godot project automatically:
yaml
- name: export game
id: export
# Use latest version (see releases for all versions)
uses: firebelley/godot-export@v4.1.1
with:
# Defining all the required inputs
godot_executable_download_url: https://github.com/godotengine/godot/releases/download/3.4.4-stable/Godot_v3.4.4-stable_linux_headless.64.zip
godot_export_templates_download_url: https://github.com/godotengine/godot/releases/download/3.4.4-stable/Godot_v3.4.4-stable_export_templates.tpz
relative_project_path: ./
archive_output: true
*YAML code snippet of the export stage of our github action *
And here's the log output of the files being zipped up:
```
⚒️ Zipping binaries
/usr/bin/7z a /home/runner/.local/share/godot/archives/webgl.zip /home/runner/.local/share/godot/builds/webgl/*
/usr/bin/7z a /home/runner/.local/share/godot/archives/win.zip /home/runner/.local/share/godot/builds/win/*
7-Zip [64] 16.02 : Copyright (c) 1999-2016 Igor Pavlov : 2016-05-21
p7zip Version 16.02 (locale=C.UTF-8,Utf16=on,HugeFiles=on,64 bits,2 CPUs Intel(R) Xeon(R) Platinum 8171M CPU @ 2.60GHz (50654),ASM,AES-NI)
Scanning the drive:
2 files, 191255312 bytes (183 MiB)
Creating archive: /home/runner/.local/share/godot/archives/win.zip
Items to compress: 2
Files read from disk: 2
Archive size: 37712907 bytes (36 MiB)
Everything is Ok
7-Zip [64] 16.02 : Copyright (c) 1999-2016 Igor Pavlov : 2016-05-21
p7zip Version 16.02 (locale=C.UTF-8,Utf16=on,HugeFiles=on,64 bits,2 CPUs Intel(R) Xeon(R) Platinum 8171M CPU @ 2.60GHz (50654),ASM,AES-NI)
Scanning the drive:
9 files, 174647145 bytes (167 MiB)
Creating archive: /home/runner/.local/share/godot/archives/webgl.zip
Items to compress: 9
Files read from disk: 9
Archive size: 28699214 bytes (28 MiB)
Everything is Ok
```
Output of the zipping sub-stage of our export stage
So what happens here? Well, to be honest, without any other work the zip files would fall into the ether, into a black hole, without any means of using them. The resulting files are saved as artifacts in the pipeline, available for download in github by any team members with access to the github repository. This got us part of the way there.
*web HTML5 and windows exe artifacts automatically produced by our pipeline *
These artifacts are saved in the github repository, one batch of artifacts for each version for archival purposes, using the following snippet:
yaml
- name: upload windows artifact
id: upload_win_artifact
uses: actions/upload-artifact@v3
with:
name: ${{ steps.tag_version.outputs.TAG_VERSION }}-win
path: ${{ steps.export.outputs.archive_directory }}/win.zip
- name: upload webgl artifact
id: upload_webgl_artifact
uses: actions/upload-artifact@v3
with:
name: ${{ steps.tag_version.outputs.TAG_VERSION }}-webgl
path: ${{ steps.export.outputs.archive_directory }}/webgl.zip
*artifact uploading YAML snippet *
The next step in our pipeline is the juicy bit: uploading the zip files automatically to itch.io. This took a few steps to set up, with the first one being getting an authentication token for itch.io's upload program, Butler. I had to download butler for my windows PC, then use butler to log into the system. Detailed instructions for logging into butler for your pipeline are here.
*butler login prompt *
If you think it's a good idea to put this API key right in your repository, think again. If your repository were compromised somebody would be able to make api calls to itch.io as though they are you, and could do nefarious things in your name, such as try to DDOS itch.io during the game jam. Github has a Secrets system to store secrets securely with one-way entering: once you enter it a human can't get it back out. After logging in, the secret api key is set up using Github Action Secrets.
*The secrets we had set up for our pipeline. not even I can read them now. *
Now that our secrets were set up, we used the following snippet to upload our artifacts to itch.io automatically:
yaml
- name: download-artifact-win
id: artifact_win_download
uses: actions/download-artifact@v2.0.8
with:
name: ${{ steps.tag_version.outputs.TAG_VERSION }}-win
path: build/win
- name: list artifact
id: list_artifact
run: ls -lha ./build/win
- name: push windows to itch
id: itch_win_push
uses: KikimoraGames/itch-publish@v0.0.3
with:
butlerApiKey: ${{secrets.BUTLER_API_KEY}}
gameData: ./build/win/win.zip
itchUsername: ${{secrets.ITCH_USERNAME}}
itchGameId: ${{ secrets.ITCH_GAME_ID }}
buildChannel: windows
buildNumber: ${{ steps.tag_version.outputs.TAG_VERSION }}
- name: download-artifact-webgl
id: artifact_webgl_download
uses: actions/download-artifact@v2.0.8
with:
name: ${{ steps.tag_version.outputs.TAG_VERSION }}-webgl
path: build/webgl
- name: push webgl to itch
id: itch_webgl_push
uses: KikimoraGames/itch-publish@v0.0.3
with:
butlerApiKey: ${{secrets.BUTLER_API_KEY}}
gameData: ./build/webgl/webgl.zip
itchUsername: ${{secrets.ITCH_USERNAME}}
itchGameId: ${{ secrets.ITCH_GAME_ID }}
buildChannel: webgl
buildNumber: ${{ steps.tag_version.outputs.TAG_VERSION }}
push to itch stages of our pipeline
For the windows and html5 exports each, it downloads the packaged artifact then used KikimoraGames/itch-publish to automatically publish the zip files to itch.io. We're almost there.
As-is, the uploads aren't ready for consumption and the itch.io page needed some configuration for the file downloads. The html5 download had to be configured as an HTML5 download in order for it to appear in a player on the page. With this step done, every time we tagged a commit and pushed the tags, our itch.io page was updated minutes later We could ask our friends and play testers to test our game in progress and they could provide feedback.
*our file configuratons for our automatically uploaded artifacts *
In the end, we tagged our repository 112 times since friday afternoon, which resulted in 112 releases. We were confident that we could ship the game 20 minutes before the cut-off because we had done it 112 times already, and knew exactly how long the pipeline would take. We even snuck in an extra release with some minor tweaks for good measure with 44 seconds to go and hit our publish button on the submission.
* multiple releases per hour means feedback multiple times per hour *
Nobody on our team had made a game before with this many people and having this tooling to continually integrate our work, and continually test it, then continually package it up, let us work together with confidence. It's free to use, with thousands of free pipeline minutes included with github and 500mb of free storage.
Next game jam, get immediate feedback by setting up your own pipeline and leave the stress of packaging behind. Focus on your deliverables and not your delivery. I hope this is helpful for others at the next jam and if you have questions feel free to leave them on the post.