Wails 실전 강좌 #6 CI/CD 자동 릴리스 — GitHub Actions로 세 플랫폼 배포
1부의 마지막 글입니다. #5에서 서명·공증을 익혔으니, 이제 이 전 과정을 자동화합니다. 목표는 명확합니다. Git 태그 하나를 밀면 macOS·Windows·Linux 세 플랫폼의 서명된 릴리스가 자동으로 만들어지는 것입니다. 손으로 세 번 빌드하고 서명하던 일을 GitHub Actions에 넘깁니다.
총 10편, 2부 구성이며 이 글은 1부의 마지막입니다.
- 1부 — 앱을 만들고 배포하기: #1 · #2 · #3 · #4 · #5 서명과 공증 · #6 CI/CD 자동 릴리스 ← 이번 글
- 2부 — 완성도와 리피터: #7 노트 에디터 실전 · #8 테스트 전략 · #9 자동 업데이트 구현 · #10 마감과 완성도
왜 매트릭스인가: macOS는 크로스컴파일이 안 된다 #
입문 #6에서 언급했듯, 데스크톱 앱은 한 OS에서 세 OS용 바이너리를 뽑기 어렵습니다. 특히 macOS 앱은 macOS에서만 빌드할 수 있습니다. 서명·공증 도구(codesign, notarytool)가 macOS 전용이기 때문입니다. 그래서 한 러너에서 크로스컴파일하는 대신, 플랫폼마다 그 OS의 러너에서 빌드하는 매트릭스를 씁니다. GitHub Actions는 macos, windows, ubuntu 러너를 모두 제공하므로 이 구조가 자연스럽습니다.
여기서 #2의 선택이 보상을 줍니다. SQLite를 순수 Go 드라이버 modernc.org/sqlite로 골랐기 때문에 각 러너에서 CGO 없이 그냥 빌드됩니다. CGO판을 골랐다면 러너마다 C 툴체인을 맞추느라 워크플로가 훨씬 복잡해졌을 것입니다.
워크플로: 태그에 반응하는 매트릭스 #
v로 시작하는 태그가 푸시되면 세 러너가 병렬로 빌드합니다.
on:
push:
tags: ["v*"]
jobs:
build:
strategy:
matrix:
include:
- os: macos-latest
platform: darwin/universal
- os: windows-latest
platform: windows/amd64
- os: ubuntu-latest
platform: linux/amd64
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with: { go-version: "1.23" }
- name: Wails 빌드
uses: dAppServer/wails-build-action@main
with:
build-platform: ${{ matrix.platform }}dAppServer/wails-build-action은 Wails CLI 설치, 프론트엔드 의존성 설치, 빌드를 묶어 처리하는 커뮤니티 액션입니다. 직접 wails build를 부르는 단계로 대체해도 되지만, 이 액션이 서명·설치 파일 생성까지 옵션으로 지원해 초기 구성을 줄여 줍니다.
서명을 CI로: Secrets에서 복원 #
#5에서 비밀을 로컬에 두지 않기로 한 것이 여기서 이어집니다. 인증서는 base64로 인코딩해 GitHub Secrets에 넣고, 워크플로에서 복원해 서명에 씁니다.
- name: 인증서 가져오기 (macOS)
if: matrix.os == 'macos-latest'
env:
CERT_P12: ${{ secrets.MACOS_CERT_P12 }} # base64 인코딩된 .p12
CERT_PW: ${{ secrets.MACOS_CERT_PASSWORD }}
run: |
echo "$CERT_P12" | base64 --decode > cert.p12
security create-keychain -p "" build.keychain
security import cert.p12 -k build.keychain -P "$CERT_PW" -T /usr/bin/codesign
# 이후 codesign → notarytool submit --wait → stapler staple (#5 참조)Windows 러너도 같은 방식으로 코드 서명 인증서를 복원해 signtool로 서명합니다. 민감한 값은 모두 secrets.*에서만 오고, 로그에 찍히지 않도록 주의합니다.
릴리스로 묶기 #
각 러너가 만든 산출물을 하나의 GitHub Release에 모읍니다. 빌드 산출물을 아티팩트로 올린 뒤, 마지막 잡에서 릴리스를 만들어 붙이거나, 각 잡이 같은 태그의 릴리스에 자산을 추가하는 방식을 씁니다.
- name: 릴리스에 자산 업로드
uses: softprops/action-gh-release@v2
with:
files: build/bin/*이제 사이클은 이렇습니다. 코드를 커밋하고 git tag v1.0.0 && git push --tags를 하면, 세 러너가 각자 빌드·서명하고, 완성된 설치 파일이 v1.0.0 릴리스 페이지에 올라갑니다. 사용자는 자기 OS에 맞는 파일을 내려받아 경고 없이 설치합니다.
자동 업데이트: 무엇을 쓸 수 있나 #
앱을 배포한 뒤의 다음 질문은 자동 업데이트입니다. Wails에는 공식 내장 업데이터가 없으므로 전략을 골라야 합니다.
- 가벼운 방식: 앱이 시작할 때 GitHub Releases의 최신 태그를 조회해, 현재 버전보다 높으면 릴리스 페이지를 열어 사용자가 직접 받게 안내합니다. 구현이 단순하고 서명 흐름과 충돌하지 않습니다.
- 완전 자동: 새 바이너리를 내려받아 스스로 교체하는 방식입니다. 서명 검증, 권한, 롤백까지 직접 다뤄야 해서 복잡하고, 잘못하면 서명된 앱의 신뢰를 깨뜨릴 수 있습니다.
개인·소규모 배포라면 가벼운 방식(업데이트 있음을 알리고 릴리스로 보내기)이 안전한 기본값입니다. 완전 자동 교체는 사용자 규모가 커져 수동 업데이트 안내가 부담이 될 때 검토합니다.
1부를 마치며 — 그리고 2부 #
여기까지 1부에서 메모리 기반 뼈대를 실제 배포 가능한 노트 앱으로 키웠습니다. 층을 나누고(#1), 순수 Go SQLite를 붙이고(#2), 검색과 데이터 흐름을 잡고(#3), 백그라운드에 상주시키고(#4), 서명·공증하고(#5), CI로 자동화(#6)해, 아이디어를 세 플랫폼의 서명된 설치 파일로 배포하는 전체 사이클을 손에 넣었습니다.
그런데 배포 가능한 앱과 사용자가 매일 다시 여는 앱은 다릅니다. 지금까지 백엔드에 치우쳐 뒤로 미룬 앞단(에디터·UX), 약속만 한 테스트, 전략만 이야기한 자동 업데이트, 그리고 설정·다크모드·데이터 안전 같은 마감이 남아 있습니다. 2부(#7〜#10)에서 이 앱을 리피터가 생기는 완성도까지 끌어올립니다.
정리 #
- macOS 앱은 macOS에서만 빌드·서명되므로, 한 러너 크로스컴파일 대신 플랫폼별 러너 매트릭스를 씁니다. 순수 Go SQLite 덕에 각 러너 빌드가 단순합니다.
- 워크플로는
v*태그에 반응해 세 러너에서 병렬 빌드하고,wails-build-action이 빌드·설치 파일 생성을 묶어 줍니다. - 서명 비밀은 base64로 GitHub Secrets에 넣고 러너에서 복원해 씁니다. 로컬에 두지 않는 #5의 원칙이 CI에서 완성됩니다.
- Wails에는 공식 업데이터가 없습니다. 개인·소규모는 “업데이트 있음을 알리고 릴리스로 보내기"가 안전한 기본값이고, 완전 자동 교체는 규모가 커질 때 검토합니다.
- 태그 하나로 세 플랫폼 서명 릴리스가 자동 생성되는 지점이, 이 시리즈가 목표한 배포 자동화의 완성입니다.