Skip to content
BAEM1N.DEV — AI, RAG, LLMOps 개발 블로그
Go back

DeepCoWork #12: GitHub Actions Cross-Platform Build -- PyInstaller Sidecar, CI/CD

TL;DR: GitHub Actions matrix builds produce PyInstaller sidecar + Tauri app for 3 OSes, completing in 8-12 minutes with cache hits.

Table of contents

Open Table of contents

Build Pipeline

Tag push (v*)
    |
    v
build job (3x parallel)
    +-- macOS-arm64  (macos-latest)
    +-- Linux-x64    (ubuntu-latest)
    +-- Windows-x64  (windows-latest)
    |
    | Per runner:
    +-- 1. Install system dependencies
    +-- 2. Setup Rust + Node.js + Python
    +-- 3. python bundle.py -> agent-server-{target}
    +-- 4. npm ci + tauri build -> installer
    |
    v
release job
    +-- Download all artifacts
    +-- Create GitHub Release (draft)

Workflow Trigger

on:
  push:
    tags: ['v*']
  workflow_dispatch:
    inputs:
      deploy_mode:
        description: 'Deploy mode (all, local, cloud)'
        default: 'all'

Auto-triggered on tag pushes (v1.0.0 etc.), with manual dispatch also available. The deploy_mode input controls build variants. The GitHub Actions workflow docs and tauri-action plugin are the key references.

Build Matrix

strategy:
  fail-fast: false
  matrix:
    include:
      - platform: macos-latest
        target: aarch64-apple-darwin
        label: macOS-arm64
      - platform: ubuntu-latest
        target: x86_64-unknown-linux-gnu
        label: Linux-x64
      - platform: windows-latest
        target: x86_64-pc-windows-msvc
        label: Windows-x64

fail-fast: false ensures remaining platform builds continue if one fails.

PyInstaller Sidecar Build

bundle.py runs on each platform:

def build():
    target = get_target_triple()
    cmd = [
        sys.executable, "-m", "PyInstaller",
        "--onefile",
        "--name", f"agent-server-{target}",
        "--hidden-import", "uvicorn.logging",
        "--hidden-import", "aiosqlite",
        "--hidden-import", "deepagents",
        "--collect-submodules", "deepagents",
        "--collect-submodules", "langgraph",
        "--collect-submodules", "langchain_core",
        "main.py",
    ]
    subprocess.run(cmd)

    # Copy to Tauri binaries directory
    src = Path(f"dist/agent-server-{target}{exe_suffix}")
    dst = Path("../app/src-tauri/binaries") / f"agent-server-{target}{exe_suffix}"
    shutil.copy2(src, dst)

Key: --hidden-import and --collect-submodules ensure PyInstaller catches dynamic imports. FastAPI (uvicorn) and the LangChain ecosystem use dynamic imports extensively.

CI Steps in Detail

System Dependencies (Linux)

Tauri requires WebKitGTK, GTK, and appindicator on Linux:

- name: Install Linux dependencies
  if: matrix.platform == 'ubuntu-latest'
  run: |
    sudo apt-get install -y \
      libwebkit2gtk-4.1-dev librsvg2-dev \
      patchelf libssl-dev libgtk-3-dev

Rust Cache

- uses: swatinem/rust-cache@v2
  with:
    workspaces: app/src-tauri

Rust compilation cache cuts build time by more than half after the first build.

Tauri Build

- uses: tauri-apps/tauri-action@v0
  env:
    VITE_DEPLOY_MODE: ${{ env.DEPLOY_MODE }}
  with:
    projectPath: app
    args: --target ${{ matrix.target }}

Artifacts and Release

Build artifacts are uploaded per platform, then the release job downloads all artifacts and creates a draft GitHub Release with auto-generated release notes.

Build Outputs

PlatformFormatContents
macOS.dmgApp bundle + agent-server-aarch64-apple-darwin
Linux.deb, .AppImageBinary + agent-server-x86_64-unknown-linux-gnu
Windows.msi, .exeInstaller + agent-server-x86_64-pc-windows-msvc.exe

Users need no Python, Node.js, or Rust installed — just run the installer.

Troubleshooting

IssueCauseFix
PyInstaller missing moduleDynamic importAdd --hidden-import
Tauri sidecar not foundName mismatchCheck agent-server-{target-triple} format
Linux build failureWebKitGTK missingInstall libwebkit2gtk-4.1-dev
macOS code signingNo certificateNotarization setup needed (not yet supported)

Benchmark

MetricValue
First build time (including Rust compilation)15-20 minutes
Cached build time8-12 minutes
PyInstaller sidecar build time~3 minutes
Final .dmg size (macOS arm64)~110MB
Final .msi size (Windows x64)~125MB
Final .deb size (Linux x64)~105MB

Lessons Learned

The macOS x64 build failed because macos-latest had already switched to ARM64 (Apple Silicon) runners, so PyInstaller generated an aarch64-apple-darwin binary. Tauri expected --target x86_64-apple-darwin but the sidecar binary name was aarch64, causing a mismatch. We dropped macOS Intel builds and went ARM64-only, since the Intel Mac user base had already shrunk significantly.

The second issue was PyInstaller missing dynamic imports. The LangChain and FastAPI (uvicorn) ecosystems use dynamic imports extensively, so we went through 7-8 cycles of “build, run, check error, add --hidden-import.” Frequently missed modules included uvicorn.logging, aiosqlite, and langchain_anthropic.

Third, on Windows we forgot to append the .exe suffix to the PyInstaller binary name, causing Tauri’s sidecar lookup to fail. A simple omission in the get_target_triple() function’s Windows branch — but since it only reproduced in CI and not locally, the feedback loop was painfully long.

FAQ

What about macOS Intel builds?

Currently Apple Silicon (aarch64) only. Add x86_64-apple-darwin to the matrix for Intel support.

How long does a build take?

First build: 15-20 minutes (includes Rust compilation). With cache: 8-12 minutes. The PyInstaller sidecar build takes the longest.

Is auto-update supported?

Not yet. Implementable with Tauri’s tauri-plugin-updater by hosting a release JSON file on GitHub Pages.


Series

  1. DeepCoWork: I Built an AI Agent Desktop App
  2. Tauri 2 + Python Sidecar
  3. DeepAgents SDK Internals
  4. System Prompt Design per Mode
  5. SSE Streaming Pipeline
  6. HITL Approval Flow
  7. Multi-Agent ACP Mode
  8. Agent Memory 4 Layers
  9. Skills System
  10. LLM Provider Integration
  11. Security Checklist
  12. [This post] GitHub Actions Cross-Platform Build

AI-assisted content
Share this post on:

Next Post
DeepCoWork #11: Security Checklist -- Path Traversal, Input Validation, CSP, CORS