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

DeepCoWork #12: GitHub Actions 크로스 플랫폼 빌드 -- PyInstaller 사이드카, CI/CD

TL;DR: GitHub Actions 매트릭스 빌드로 3개 OS에서 PyInstaller sidecar + Tauri 앱을 빌드하며, 캐시 적중 시 8~12분에 완료된다.

Table of contents

Open Table of contents

빌드 파이프라인

Tag push (v*)
    |
    v
build job (3x parallel)
    +-- macOS-arm64  (macos-latest)
    +-- Linux-x64    (ubuntu-latest)
    +-- Windows-x64  (windows-latest)
    |
    | 각 러너에서:
    +-- 1. 시스템 의존성 설치
    +-- 2. Rust + Node.js + Python 세팅
    +-- 3. python bundle.py -> agent-server-{target}
    +-- 4. npm ci + tauri build -> 설치 파일
    |
    v
release job
    +-- 모든 아티팩트 다운로드
    +-- GitHub Release (draft) 생성

워크플로 트리거

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

태그 푸시(v1.0.0 등)로 자동 트리거되고, workflow_dispatch로 수동 실행도 가능하다. deploy_mode 입력으로 빌드 변형을 제어한다. GitHub Actions 워크플로 문서tauri-action 플러그인이 핵심 참고 자료다.

빌드 매트릭스

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로 하나가 실패해도 나머지 플랫폼 빌드가 계속된다.

PyInstaller 사이드카 빌드

bundle.py가 각 플랫폼에서 실행된다:

def get_target_triple() -> str:
    system = platform.system()
    machine = platform.machine().lower()
    if system == "Darwin":
        arch = "aarch64" if machine == "arm64" else "x86_64"
        return f"{arch}-apple-darwin"
    elif system == "Linux":
        return "x86_64-unknown-linux-gnu"
    elif system == "Windows":
        return "x86_64-pc-windows-msvc"

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

    # Tauri binaries 디렉토리에 복사
    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)

핵심: --hidden-import--collect-submodules가 PyInstaller가 동적 임포트를 놓치지 않게 한다. FastAPI(uvicorn)와 LangChain 생태계는 동적 임포트를 많이 사용한다.

CI 스텝 상세

시스템 의존성 (Linux)

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

Tauri가 Linux에서 WebKitGTK, GTK, appindicator를 요구한다.

Rust 캐시

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

Rust 컴파일 캐시로 2차 빌드 이후 빌드 시간을 절반 이상 단축한다.

Tauri 빌드

- name: Build Tauri app
  uses: tauri-apps/tauri-action@v0
  env:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    VITE_DEPLOY_MODE: ${{ env.DEPLOY_MODE }}
  with:
    projectPath: app
    tauriScript: npm run tauri
    args: --target ${{ matrix.target }}

VITE_DEPLOY_MODE가 빌드 시 프로바이더 노출을 제어한다.

아티팩트와 릴리스

아티팩트 업로드

- name: Upload build artifacts
  uses: actions/upload-artifact@v4
  with:
    name: DeepCoWork-${{ matrix.label }}
    path: |
      app/src-tauri/target/${{ matrix.target }}/release/bundle/**/*.dmg
      app/src-tauri/target/${{ matrix.target }}/release/bundle/**/*.deb
      app/src-tauri/target/${{ matrix.target }}/release/bundle/**/*.msi

릴리스 생성

release:
  if: startsWith(github.ref, 'refs/tags/v')
  needs: build
  runs-on: ubuntu-latest
  steps:
    - uses: actions/download-artifact@v4
      with: { path: artifacts }
    - uses: softprops/action-gh-release@v2
      with:
        files: artifacts/**/*
        generate_release_notes: true
        draft: true

태그 푸시 시에만 릴리스가 생성되고, draft: true로 수동 확인 후 게시한다.

빌드 결과물

플랫폼형식포함 내용
macOS.dmg앱 번들 + agent-server-aarch64-apple-darwin
Linux.deb, .AppImage바이너리 + agent-server-x86_64-unknown-linux-gnu
Windows.msi, .exe설치 파일 + agent-server-x86_64-pc-windows-msvc.exe

사용자는 Python, Node.js, Rust를 설치할 필요 없이 설치 파일만 실행하면 된다.

트러블슈팅

문제원인해결
PyInstaller 누락 모듈동적 임포트--hidden-import 추가
Tauri sidecar 미발견이름 불일치agent-server-{target-triple} 형식 확인
Linux 빌드 실패WebKitGTK 미설치apt-get install libwebkit2gtk-4.1-dev
macOS 코드사인인증서 미설정Notarization 설정 필요 (현재 미지원)

실측 데이터

항목수치
초기 빌드 시간 (Rust 컴파일 포함)15~20분
캐시 적중 빌드 시간8~12분
PyInstaller sidecar 빌드 시간~3분
최종 .dmg 크기 (macOS arm64)~110MB
최종 .msi 크기 (Windows x64)~125MB
최종 .deb 크기 (Linux x64)~105MB

삽질 노트

macOS x64 빌드가 실패했다. macos-latest가 이미 ARM64(Apple Silicon) 러너로 바뀌어서 PyInstaller가 aarch64-apple-darwin 바이너리를 생성했기 때문이다. Tauri는 --target x86_64-apple-darwin을 기대하는데 sidecar 바이너리 이름이 aarch64라 매칭이 안 됐다. 결국 macOS Intel 빌드를 드롭하고 ARM64만 지원하기로 결정했다. Intel Mac 사용자 비율이 이미 매우 낮았기 때문이다.

두 번째 삽질은 PyInstaller의 동적 임포트 누락이었다. LangChain과 FastAPI(uvicorn) 생태계는 동적 임포트를 많이 사용해서, --hidden-import를 하나씩 추가하며 “빌드→실행→에러 확인→추가” 사이클을 7~8번 반복했다. uvicorn.logging, aiosqlite, langchain_anthropic 등이 자주 누락되는 모듈이었다.

세 번째로, Windows에서 PyInstaller 바이너리 이름에 .exe 확장자를 안 붙였더니 Tauri sidecar 탐색이 실패했다. get_target_triple() 함수에서 Windows일 때 .exe suffix를 추가하는 분기를 빠뜨린 단순한 실수였는데, CI에서만 재현되어 로컬에서 디버깅할 수 없어 피드백 루프가 길었다.

자주 묻는 질문

macOS Intel 빌드는?

현재 Apple Silicon(aarch64)만 지원한다. Intel 빌드를 추가하려면 매트릭스에 x86_64-apple-darwin 타겟을 추가하면 된다.

빌드 시간은 얼마나 걸리나?

초기 빌드: 15-20분 (Rust 컴파일 포함). 캐시 적중 시: 8-12분. PyInstaller 사이드카 빌드가 가장 시간이 오래 걸린다.

자동 업데이트를 지원하나?

아직 미지원. Tauri의 tauri-plugin-updater로 구현할 수 있고, 릴리스 JSON 파일을 GitHub Pages에 호스팅하면 된다.


시리즈 목차

  1. DeepCoWork: AI 에이전트 데스크톱 앱을 만들었다
  2. Tauri 2 + Python 사이드카
  3. DeepAgents SDK 핵심 해부
  4. 모드별 시스템 프롬프트 설계
  5. SSE 스트리밍 파이프라인
  6. HITL 승인 플로우
  7. 멀티에이전트 ACP 모드
  8. 에이전트 메모리 4계층
  9. 스킬 시스템
  10. LLM 프로바이더 통합
  11. 보안 체크리스트
  12. [이번 글] GitHub Actions 크로스 플랫폼 빌드

AI-assisted content
Share this post on:

Previous Post
Qwen3.5 로컬 추론 벤치마크 결과표: 4대 하드웨어 × 5개 엔진
Next Post
DeepCoWork #11: 보안 체크리스트 -- 경로 탈출, 입력 검증, CSP, CORS