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
- Review the patterns and examples below
- Apply the relevant patterns to your implementation
- 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
| Target | Runner | Build Command |
|---|---|---|
| darwin-x64 | macos-latest | cargo build --target x86_64-apple-darwin |
| darwin-arm64 | macos-latest | cargo build --target aarch64-apple-darwin |
| linux-x64 | ubuntu-latest | cargo build --target x86_64-unknown-linux-gnu |
| linux-arm64 | ubuntu-latest | Cross-compile or ARM runner |
| linux-x64-musl | ubuntu-latest | cargo build --target x86_64-unknown-linux-musl |
| win32-x64 | windows-latest | cargo 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
| Channel | Purpose | Update Frequency |
|---|---|---|
| stable | Production releases | Release-triggered |
| latest | Most recent release | Every release |
| beta | Pre-release testing | Before stable |
| nightly | Continuous integration | Daily |
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 createonly - No versioning → Tag-based releases
- Container-only → Docker build workflow
Anti-Patterns (Avoid)
| Anti-Pattern | Problem | Solution |
|---|---|---|
| Single-job build | No parallelization, slow | Use matrix strategy for parallel builds |
| Missing retry logic | Transient failures = manual intervention | Add retry policy to workflow steps |
| No artifact retention | Artifacts lost after 90 days | Set retention-days explicitly |
| Unsigned binaries | Security warnings, trust issues | Sign all production binaries |
| No checksum verification | Tampered downloads undetected | Generate SHA256SUMS for all artifacts |
| Hardcoded versions | Manual version updates | Extract from git tag dynamically |
| No rollback procedure | Failed releases require manual fix | Document and test rollback to previous version |
| Missing channel strategy | No stable vs beta separation | Implement 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