Automating Multi-Platform Tool Releases Using GitHub Actions
Releasing software for multiple platforms can be challenging without automation. With GitHub Actions, we can streamline this process.

Specialized in uncovering vulnerabilities within software supply chains and dependency ecosystems. Creator of SCAGoat and other open-source security tools. Speaker at Black Hat, DEF CON, and AppSec conferences with research on malicious package detection, dependency confusion, and CI/CD security.
In this blog, we will explore how to leverage GitHub Actions for building and releasing tools written in languages such as Go, Python, Rust, and more and this will help you master the process of creating your tools binary releases that meet multi-platform needs.
Prerequisites
Before starting, let’s get ready with the basic setup:
A GitHub repository with Python project (e.g., a tool or CLI app).
Installed Git and set up a local development environment.
A basic understanding of GitHub Actions YAML syntax.
Setting Up Our Python Script
Let’s try to create a simple Python script named hello.py. This will be our example application:
# hello.py
def main():
print("Hello, GitHub Actions!")
if __name__ == "__main__":
main()
Our sample application code is ready, Now, let’s push this to our GitHub repository.
Writing the GitHub Actions Workflow 🔧
Let’s create a directory .github/workflows in the root of our repository and add a file named release.yml. This workflow is triggered whenever we push a new version tag, and it automates the build and release process.
.gitlab-ci.yml file for CI/CD configurations and Bitbucket uses the bitbucket-pipelines.yml file, both typically placed in the root directory of the repository.Here’s the complete release.yml workflow:
name: Release
on:
push:
tags:
- 'v*' # Trigger release when a new tag is pushed
jobs:
release:
name: Release - ${{ matrix.platform.release_for }}
runs-on: ${{ matrix.platform.os }}
strategy:
matrix:
platform:
- release_for: Linux-x86_64
os: ubuntu-latest
pyinstaller_target: linux
bin: hello
name: hello-linux-amd64
- release_for: Windows-x86_64
os: windows-latest
pyinstaller_target: windows
bin: hello.exe
name: hello-windows-amd64.exe
- release_for: macOS-x86_64
os: macos-latest
pyinstaller_target: macos
bin: hello
name: hello-macos-amd64
steps:
- uses: actions/checkout@v2 # Checkout code
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.9' # Set up the Python environment
- name: Install PyInstaller
run: |
python -m pip install --upgrade pip
pip install pyinstaller # Install PyInstaller for packaging
- name: Build Executable
run: |
# Build the executable with PyInstaller for each platform (adjusting accordingly)
pyinstaller --onefile hello.py
working-directory: .
- name: Prepare assets
shell: bash
run: |
# Prepare the release folder
mkdir -p release
# Move the binary from PyInstaller dist directory to release folder
cp dist/hello* release/${{ matrix.platform.name }}
- name: Upload binaries to GitHub release
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: release/${{ matrix.platform.name }}
asset_name: ${{ matrix.platform.name }}
tag: ${{ github.ref }}
overwrite: true
Create and Push a GitHub Release Tag
For this workflow to trigger, we must create a version tag in this repository. Here’s how to do it via the CLI:
Commit all changes:
git add . git commit -m "Add release workflow and script"Create a new version tag:
git tag -a v1.0.0 -m "First release with GitHub Actions"Push the tag to trigger the workflow:
git push origin --tagsThis will start the release workflow in GitHub Actions. we can monitor the progress under the "Actions" tab of our repository.
Now, Let’s understand the workflow 🔍
What Does Each Step Do?
Trigger on Tags
The workflow is triggered when a tag matching the patternv*is pushed. This ensures it only runs for versioned releases, like in our case we triggered withgit tagand pushed the code in repository with tags.on: push: tags: - 'v*' # Trigger release when a new tag is pushedMatrix for Multi-Platform Builds
Usingmatrix.platform, the workflow specifies OS and configuration settings for Linux, macOS, and Windows. For more details about matrix uses, there is an awesome guide from GithubBuild with PyInstaller
PyInstaller creates a single executable binary (--onefileoption) tailored for each platform.Package Artifacts for Release
The prepared binaries are moved to arelease/directory with platform-specific names.Upload Release Assets
Theupload-release-actionpushes the compiled binaries to the GitHub release, making them available for download.
After the workflow completes:
Navigate to the Releases section of the repository.
Find the latest release created by the workflow.
Download the platform-specific binary from the release assets.
Wrapping Up 🎉
In this article, we demonstrated how to create platform-specific Python executables and automate their release using GitHub Actions. The example release.yml workflow builds, packages, and uploads binaries to GitHub for Windows, macOS, and Linux users seamlessly.
The same strategies in matrix platform can be used for the other language releases as well in order to have the platform specific binaries for your tools. GitHub Actions empowers developers to automate tedious tasks and streamline delivery pipelines, leaving more time for building features and solving user problems. Give it a try and let your tools shine across platforms!
Have feedback or questions? Drop them in the comments!





