Skip to main content

Binary Release Workflow Skill

Binary Release Workflow Skill

When to Use This Skill

Use this skill when implementing binary release workflow patterns in your codebase.

How to Use This Skill

  1. Review the patterns and examples below
  2. Apply the relevant patterns to your implementation
  3. Follow the best practices outlined in this skill

Overview

This skill provides patterns for automating multi-platform binary releases through CI/CD pipelines. It covers matrix builds, artifact management, signing integration, manifest generation, and CDN publishing.

Quick Reference

Release Pipeline Stages

┌─────────┐   ┌─────────┐   ┌─────────┐   ┌─────────┐   ┌─────────┐
│ Build │ → │ Test │ → │ Sign │ → │Manifest │ → │ Publish │
└─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘
│ │ │ │ │
6 platforms Integration 3 platforms Checksums CDN
in parallel tests in parallel + versions + npm

Platform Matrix

TargetRunnerBuild Command
darwin-x64macos-latestcargo build --target x86_64-apple-darwin
darwin-arm64macos-latestcargo build --target aarch64-apple-darwin
linux-x64ubuntu-latestcargo build --target x86_64-unknown-linux-gnu
linux-arm64ubuntu-latestCross-compile or ARM runner
linux-x64-muslubuntu-latestcargo build --target x86_64-unknown-linux-musl
win32-x64windows-latestcargo build --target x86_64-pc-windows-msvc

Level 1: Essential Patterns

Basic Matrix Build

name: Release

on:
push:
tags: ['v*']

jobs:
build:
strategy:
fail-fast: false
matrix:
include:
- os: macos-latest
target: darwin-x64
- os: macos-latest
target: darwin-arm64
- os: ubuntu-latest
target: linux-x64
- os: windows-latest
target: win32-x64
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4

- name: Build
run: cargo build --release

- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: binary-${{ matrix.target }}
path: target/release/myapp*

Collect and Publish

  publish:
needs: build
runs-on: ubuntu-latest
steps:
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: dist/
pattern: binary-*
merge-multiple: true

- name: Create release
uses: softprops/action-gh-release@v1
with:
files: dist/*

Level 2: Production Patterns

Complete Release Pipeline

name: Release Pipeline

on:
push:
tags: ['v*']

permissions:
contents: write

env:
PRODUCT_NAME: coditect
CDN_BUCKET: gs://coditect-dist

jobs:
# Stage 1: Build all platforms in parallel
build:
strategy:
fail-fast: false
matrix:
include:
- os: macos-latest
target: darwin-x64
rust_target: x86_64-apple-darwin
- os: macos-latest
target: darwin-arm64
rust_target: aarch64-apple-darwin
- os: ubuntu-latest
target: linux-x64
rust_target: x86_64-unknown-linux-gnu
- os: ubuntu-latest
target: linux-arm64
rust_target: aarch64-unknown-linux-gnu
cross: true
- os: ubuntu-latest
target: linux-x64-musl
rust_target: x86_64-unknown-linux-musl
- os: windows-latest
target: win32-x64
rust_target: x86_64-pc-windows-msvc
ext: .exe

runs-on: ${{ matrix.os }}
outputs:
version: ${{ steps.version.outputs.version }}

steps:
- uses: actions/checkout@v4

- name: Get version from tag
id: version
run: echo "version=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT

- name: Setup Rust
uses: dtolnay/rust-action@stable
with:
targets: ${{ matrix.rust_target }}

- name: Install cross (if needed)
if: matrix.cross
run: cargo install cross

- name: Build
run: |
if [ "${{ matrix.cross }}" = "true" ]; then
cross build --release --target ${{ matrix.rust_target }}
else
cargo build --release --target ${{ matrix.rust_target }}
fi
shell: bash

- name: Package binary
run: |
mkdir -p dist
BINARY_NAME="${{ env.PRODUCT_NAME }}-${{ matrix.target }}${{ matrix.ext }}"
cp target/${{ matrix.rust_target }}/release/${{ env.PRODUCT_NAME }}${{ matrix.ext }} "dist/$BINARY_NAME"

# Create tarball for Unix
if [ "${{ matrix.os }}" != "windows-latest" ]; then
tar -czvf "dist/$BINARY_NAME.tar.gz" -C dist "$BINARY_NAME"
fi
shell: bash

- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: binary-${{ matrix.target }}
path: dist/*
retention-days: 7

# Stage 2: Sign binaries
sign:
needs: build
strategy:
matrix:
include:
- os: macos-latest
platform: darwin
- os: windows-latest
platform: win32
- os: ubuntu-latest
platform: linux
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4

- name: Download artifacts
uses: actions/download-artifact@v4
with:
path: dist/
pattern: binary-${{ matrix.platform }}*
merge-multiple: true

- name: Sign (macOS)
if: matrix.platform == 'darwin'
env:
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
run: |
# Import certificate to keychain
echo "$APPLE_CERTIFICATE" | base64 --decode > certificate.p12
security create-keychain -p "" build.keychain
security default-keychain -s build.keychain
security unlock-keychain -p "" build.keychain
security import certificate.p12 -k build.keychain -P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple: -s -k "" build.keychain

# Sign binaries
for binary in dist/*-darwin-*; do
[ -f "$binary" ] && [ "${binary##*.}" != "gz" ] && \
codesign --sign "${{ secrets.APPLE_DEVELOPER_ID }}" --options runtime --timestamp --force "$binary"
done

rm certificate.p12

- name: Sign (Windows)
if: matrix.platform == 'win32'
run: |
$certBytes = [Convert]::FromBase64String($env:WINDOWS_CERT)
[IO.File]::WriteAllBytes("cert.pfx", $certBytes)

foreach ($binary in Get-ChildItem -Path dist -Filter "*-win32-*.exe") {
& signtool.exe sign /f cert.pfx /p $env:WINDOWS_CERT_PASSWORD /fd SHA256 /tr http://timestamp.digicert.com /td SHA256 $binary.FullName
}

Remove-Item cert.pfx
env:
WINDOWS_CERT: ${{ secrets.WINDOWS_CERT_BASE64 }}
WINDOWS_CERT_PASSWORD: ${{ secrets.WINDOWS_CERT_PASSWORD }}

- name: Sign (Linux)
if: matrix.platform == 'linux'
run: |
echo "${{ secrets.GPG_PRIVATE_KEY }}" | gpg --batch --import
for binary in dist/*-linux-*; do
[ -f "$binary" ] && [ "${binary##*.}" != "gz" ] && [ "${binary##*.}" != "asc" ] && \
gpg --batch --yes --passphrase "${{ secrets.GPG_PASSPHRASE }}" --pinentry-mode loopback --armor --detach-sign "$binary"
done

- name: Upload signed artifacts
uses: actions/upload-artifact@v4
with:
name: signed-${{ matrix.platform }}
path: dist/*
retention-days: 7

# Stage 3: Generate manifest and checksums
manifest:
needs: sign
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Download all signed artifacts
uses: actions/download-artifact@v4
with:
path: dist/
pattern: signed-*
merge-multiple: true

- name: Generate checksums
run: |
cd dist
for file in *; do
[ -f "$file" ] && sha256sum "$file" >> SHA256SUMS
done

- name: Generate manifest
run: python scripts/generate-release-manifest.py \
--version ${{ needs.build.outputs.version }} \
--binaries-dir dist/ \
--cdn-base "${{ env.CDN_BUCKET }}" \
--output dist/manifest.json

- name: Upload manifest
uses: actions/upload-artifact@v4
with:
name: release-manifest
path: |
dist/manifest.json
dist/SHA256SUMS

# Stage 4: Publish to CDN and create release
publish:
needs: [build, manifest]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: dist/
merge-multiple: true

- name: Setup Google Cloud
uses: google-github-actions/setup-gcloud@v1
with:
service_account_key: ${{ secrets.GCP_SA_KEY }}

- name: Upload to CDN
run: |
VERSION="${{ needs.build.outputs.version }}"

# Upload versioned release
gsutil -m cp -r dist/* "${{ env.CDN_BUCKET }}/$VERSION/"

# Update stable channel
gsutil cp dist/manifest.json "${{ env.CDN_BUCKET }}/stable/manifest.json"

# Update latest channel
gsutil cp dist/manifest.json "${{ env.CDN_BUCKET }}/latest/manifest.json"

- name: Create GitHub Release
uses: softprops/action-gh-release@v1
with:
files: dist/*
generate_release_notes: true
fail_on_unmatched_files: false

Manifest Schema

{
"name": "product-name",
"version": "1.2.3",
"releaseDate": "2025-01-15T00:00:00Z",
"channels": ["stable", "latest"],
"binaries": {
"darwin-x64": {
"url": "https://cdn.example.com/v1.2.3/product-darwin-x64.tar.gz",
"sha256": "abc123def456...",
"size": 45678901,
"signature": "v1.2.3/product-darwin-x64.tar.gz.asc"
},
"darwin-arm64": { "..." },
"linux-x64": { "..." },
"linux-arm64": { "..." },
"linux-x64-musl": { "..." },
"win32-x64": { "..." }
},
"changelog": "https://github.com/org/repo/releases/tag/v1.2.3",
"minimumUpgradeVersion": "1.0.0"
}

CDN Directory Structure

bucket/
├── stable/
│ └── manifest.json # Points to current stable release
├── latest/
│ └── manifest.json # Points to most recent release
├── beta/
│ └── manifest.json # Points to current beta
├── v1.2.3/
│ ├── manifest.json
│ ├── SHA256SUMS
│ ├── product-darwin-x64.tar.gz
│ ├── product-darwin-arm64.tar.gz
│ ├── product-linux-x64.tar.gz
│ ├── product-linux-arm64.tar.gz
│ ├── product-linux-x64-musl.tar.gz
│ ├── product-win32-x64.exe
│ └── *.asc # GPG signatures
└── v1.2.2/
└── ...

Version Management

Semantic Versioning

# Get version from git tag
VERSION="${GITHUB_REF#refs/tags/v}"

# Validate semver format
if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]]; then
echo "Invalid version format: $VERSION"
exit 1
fi

Channel Strategy

ChannelPurposeUpdate Frequency
stableProduction releasesRelease-triggered
latestMost recent releaseEvery release
betaPre-release testingBefore stable
nightlyContinuous integrationDaily

Artifact Retention

- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: binary-${{ matrix.target }}
path: dist/*
retention-days: 7 # Short-term for CI
compression-level: 9

Rollback Procedure

# Revert to previous version
VERSION_CURRENT="1.2.3"
VERSION_ROLLBACK="1.2.2"

# Update channel pointers
gsutil cp "gs://bucket/$VERSION_ROLLBACK/manifest.json" "gs://bucket/stable/manifest.json"

# Verify
curl -s "https://cdn.example.com/stable/manifest.json" | jq .version

Monitoring and Metrics

- name: Record release metrics
run: |
curl -X POST "${{ secrets.METRICS_ENDPOINT }}" \
-H "Content-Type: application/json" \
-d '{
"event": "release",
"version": "${{ needs.build.outputs.version }}",
"platforms": 6,
"timestamp": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"
}'

Success Output

When successful, this skill MUST output:

✅ SKILL COMPLETE: binary-release-workflow

Completed:
- [x] Matrix build completed for all platforms (6/6)
- [x] Binaries signed for darwin, win32, linux
- [x] Checksums generated (SHA256SUMS)
- [x] Manifest created with CDN URLs
- [x] Published to CDN and GitHub release

Outputs:
- Binaries: darwin-{x64,arm64}, linux-{x64,arm64,x64-musl}, win32-x64
- Signatures: .asc (GPG) or codesign verification
- Manifest: manifest.json with version, checksums, URLs
- Release: GitHub release with artifacts attached
- CDN: versioned and channel directories updated

Completion Checklist

Before marking this skill as complete, verify:

  • All 6 platform targets built successfully
  • Binary signing completed for applicable platforms
  • SHA256SUMS file generated with all artifacts
  • Manifest.json includes all binaries with checksums
  • CDN upload completed to versioned directory
  • Channel pointers updated (stable/latest)
  • GitHub release created with artifacts attached
  • Rollback procedure tested and documented

Failure Indicators

This skill has FAILED if:

  • ❌ Any platform build failed
  • ❌ Signing failed for macOS/Windows/Linux
  • ❌ Checksums missing or incorrect
  • ❌ Manifest.json incomplete or invalid
  • ❌ CDN upload failed or incomplete
  • ❌ GitHub release creation failed
  • ❌ Channel pointers not updated
  • ❌ Binaries not executable on target platforms

When NOT to Use

Do NOT use this skill when:

  • Single platform release (use simple build script)
  • No signing required (development builds)
  • No CDN needed (GitHub releases only)
  • Version management not needed (one-off builds)
  • No multi-platform support required
  • Manual release process preferred

Use alternative approaches when:

  • Single platform → Simple cargo build --release
  • Dev builds → Local build without signing
  • Simple releases → gh release create only
  • No versioning → Tag-based releases
  • Container-only → Docker build workflow

Anti-Patterns (Avoid)

Anti-PatternProblemSolution
Single-job buildNo parallelization, slowUse matrix strategy for parallel builds
Missing retry logicTransient failures = manual interventionAdd retry policy to workflow steps
No artifact retentionArtifacts lost after 90 daysSet retention-days explicitly
Unsigned binariesSecurity warnings, trust issuesSign all production binaries
No checksum verificationTampered downloads undetectedGenerate SHA256SUMS for all artifacts
Hardcoded versionsManual version updatesExtract from git tag dynamically
No rollback procedureFailed releases require manual fixDocument and test rollback to previous version
Missing channel strategyNo stable vs beta separationImplement channel pointers (stable/latest/beta)

Principles

This skill embodies these CODITECT principles:

#1 Recycle → Extend → Re-Use → Create

  • Reuse GitHub Actions (upload-artifact, gh-release)
  • Extend with custom signing steps
  • Create only platform-specific logic

#9 Automation First

  • Fully automated multi-platform builds
  • Automatic signing and checksum generation
  • Self-provisioning CDN uploads

Security (CODITECT-STANDARD-SECURITY.md)

  • Code signing for all platforms
  • Checksum verification required
  • Signature verification documented

Reliability

  • Retry logic for transient failures
  • Artifact retention for recovery
  • Rollback procedure defined

Additional Resources

  • GITHUB-ACTIONS.md: Detailed workflow examples
  • TEMPLATES/: Reusable workflow files