GitHub Actions CI/CD
This guide shows how to build, sign, and publish Electron apps using GitHub Actions for all three platforms (macOS, Windows, Linux).
Basic Concepts
- Secrets — store certificates, passwords, and API tokens in GitHub repository secrets (Settings → Secrets and variables → Actions)
- Matrix builds — build for multiple platforms in parallel using a matrix strategy
GH_TOKEN— the GitHub token electron-builder uses to upload release assets. UseGITHUB_TOKEN(provided automatically) or a PAT withreposcope
Minimal Multi-Platform Workflow
This workflow builds for macOS, Windows, and Linux on every push to main and on version tags:
name: Build and Release
on:
push:
branches: [main]
tags: ['v*']
jobs:
build:
strategy:
matrix:
os: [macos-latest, windows-latest, ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- name: Install dependencies
run: npm ci
- name: Build
run: npx electron-builder --publish never
env:
# macOS signing
CSC_LINK: ${{ secrets.MAC_CSC_LINK }}
CSC_KEY_PASSWORD: ${{ secrets.MAC_CSC_KEY_PASSWORD }}
# Windows signing
WIN_CSC_LINK: ${{ secrets.WIN_CSC_LINK }}
WIN_CSC_KEY_PASSWORD: ${{ secrets.WIN_CSC_KEY_PASSWORD }}
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: dist-${{ matrix.os }}
path: dist/
Publish on Tag Push
To publish GitHub releases automatically when you push a version tag (v1.2.3):
name: Release
on:
push:
tags: ['v*']
jobs:
release:
strategy:
matrix:
os: [macos-latest, windows-latest, ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- name: Install dependencies
run: npm ci
- name: Publish
run: npx electron-builder --publish always
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# macOS signing + notarization
CSC_LINK: ${{ secrets.MAC_CSC_LINK }}
CSC_KEY_PASSWORD: ${{ secrets.MAC_CSC_KEY_PASSWORD }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
# Windows signing
WIN_CSC_LINK: ${{ secrets.WIN_CSC_LINK }}
WIN_CSC_KEY_PASSWORD: ${{ secrets.WIN_CSC_KEY_PASSWORD }}
The --publish always flag uploads artifacts to the GitHub release for the pushed tag. If no release exists, electron-builder creates a draft release.
Draft Release Workflow
A common pattern is to publish a draft release on every tagged push, then manually promote it to a full release after reviewing:
# In electron-builder.yml
publish:
provider: github
releaseType: draft
Then in GitHub: go to Releases → find the draft → click "Edit" → "Publish release".
macOS Code Signing Setup
Preparing the Certificate
# Export your Developer ID Application certificate from Keychain Access
# File → Export Items → save as .p12 with a password
# Encode to base64
base64 -i certificate.p12 | pbcopy # copies to clipboard
Add to GitHub Secrets:
MAC_CSC_LINK— the base64-encoded.p12contentMAC_CSC_KEY_PASSWORD— the certificate password
macOS Signing
Pass CSC_LINK and CSC_KEY_PASSWORD directly — electron-builder creates and manages a temporary keychain automatically:
build-mac:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- name: Install dependencies
run: npm ci
- name: Build and notarize
run: npx electron-builder --mac --publish always
env:
CSC_LINK: ${{ secrets.MAC_CSC_LINK }}
CSC_KEY_PASSWORD: ${{ secrets.MAC_CSC_KEY_PASSWORD }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
macOS Secrets Reference
| Secret | How to Get |
|---|---|
MAC_CSC_LINK | base64 -i certificate.p12 |
MAC_CSC_KEY_PASSWORD | The password you set when exporting the .p12 |
APPLE_ID | Your Apple ID email (developer account) |
APPLE_APP_SPECIFIC_PASSWORD | Create at appleid.apple.com → App-Specific Passwords |
APPLE_TEAM_ID | 10-character team ID from developer.apple.com/account |
Windows Code Signing Setup
# Encode your Windows certificate (.pfx) to base64
# PowerShell:
[Convert]::ToBase64String([IO.File]::ReadAllBytes("certificate.pfx")) | Out-File cert_encoded.txt
# bash (Linux/macOS):
base64 certificate.pfx > cert_encoded.txt
Add to GitHub Secrets:
WIN_CSC_LINK— base64-encoded.pfxcontentWIN_CSC_KEY_PASSWORD— certificate password
Using Azure Trusted Signing
- name: Build and sign (Azure)
run: npx electron-builder --win --publish always
env:
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Configure win.azureSignOptions in your electron-builder config. See Windows Code Signing.
Caching for Faster Builds
Cache npm packages and Electron binaries to avoid redundant downloads:
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm # caches ~/.npm
- name: Cache Electron
uses: actions/cache@v4
with:
path: |
~/.cache/electron
~/.cache/electron-builder
key: ${{ runner.os }}-electron-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-electron-
Linux Snapcraft Publishing
To publish to the Snap Store, add a SNAPCRAFT_STORE_CREDENTIALS secret (obtained by running snapcraft export-login -):
- name: Build and publish Snap
run: npx electron-builder --linux snap --publish always
env:
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Complete Production Workflow
A production-grade workflow with separate jobs per platform, caching, and GitHub release publishing:
name: Release
on:
push:
tags: ['v*.*.*']
permissions:
contents: write
jobs:
build-mac:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- uses: actions/cache@v4
with:
path: ~/.cache/electron
key: mac-electron-${{ hashFiles('**/package-lock.json') }}
- run: npm ci
- run: npx electron-builder --mac --publish always
env:
CSC_LINK: ${{ secrets.MAC_CSC_LINK }}
CSC_KEY_PASSWORD: ${{ secrets.MAC_CSC_KEY_PASSWORD }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
build-windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- uses: actions/cache@v4
with:
path: ~\AppData\Local\electron
key: win-electron-${{ hashFiles('**/package-lock.json') }}
- run: npm ci
- run: npx electron-builder --win --publish always
env:
WIN_CSC_LINK: ${{ secrets.WIN_CSC_LINK }}
WIN_CSC_KEY_PASSWORD: ${{ secrets.WIN_CSC_KEY_PASSWORD }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
build-linux:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- uses: actions/cache@v4
with:
path: ~/.cache/electron
key: linux-electron-${{ hashFiles('**/package-lock.json') }}
- run: npm ci
- run: npx electron-builder --linux --publish always
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Troubleshooting
"Resource not accessible by integration"
: The workflow doesn't have permission to create releases. Add permissions: contents: write to the job or workflow. Also ensure GH_TOKEN is set.
macOS: "No identity found"
: CSC_LINK or CSC_KEY_PASSWORD is wrong or missing. Verify the base64-encoded certificate decodes to a valid .p12 and that the password is correct. Run echo "$CSC_LINK" | base64 --decode | openssl pkcs12 -info -passin pass:"$CSC_KEY_PASSWORD" locally to verify.
Windows: certificate base64 too long
: Windows CI environments may truncate environment variables over 8192 characters. Re-export the .pfx without the full certificate chain included. See Code Signing.
Build fails with "Cannot find module"
: Native modules need to be rebuilt for the target Electron version. Ensure npm ci (not npm install) is used. electron-builder handles native module rebuilding automatically.
Related Pages
- Code Signing — environment variables, certificate types
- macOS Notarization — notarization setup and troubleshooting
- Auto Update — configuring electron-updater with GitHub releases
- Multi Platform Build — building for other platforms using Docker