배포는 빌드 명령 하나로 끝나지 않는다
개발 중인 Electron 앱은 npm start로 실행된다. 하지만 사용자는 소스 코드를 내려받아 터미널에서 실행하지 않는다. 사용자는 설치 파일을 받고, 앱 아이콘을 누르고, 운영체제가 보여 주는 보안 경고를 확인하고, 새 버전이 나왔을 때 자연스럽게 업데이트되기를 기대한다.
패키징은 이 전환을 담당한다. 개발 프로젝트를 운영체제가 이해하는 앱 묶음으로 만들고, 설치 프로그램을 만들고, 서명하고, 배포 서버에 올린다. 이 장의 목표는 "내 컴퓨터에서 실행되는 앱"을 "사용자에게 전달할 수 있는 제품"으로 바꾸는 기준을 세우는 것이다.
AI 생성 이미지
Electron Forge와 electron-builder 선택하기
Electron 앱을 패키징할 때 가장 자주 만나는 도구는 Electron Forge와 electron-builder다. 둘 다 Windows, macOS, Linux 배포물을 만들 수 있지만 접근 방식이 다르다.
| 항목 | Electron Forge | electron-builder |
|---|---|---|
| 구성 방식 | maker와 publisher를 조합하는 방식 | 하나의 build 설정에 대상과 배포 옵션을 모으는 방식 |
| 장점 | Electron 공식 튜토리얼과 흐름이 잘 맞고, 구성 요소를 교체하기 좋다 | 설치 파일, 코드 서명, 공증, 업데이트 게시를 한 설정 안에서 다루기 좋다 |
| 적합한 경우 | Electron 기본 구조를 따라가며 필요한 maker를 명시적으로 붙이고 싶을 때 | 여러 운영체제 산출물과 자동 업데이트까지 한 번에 관리하고 싶을 때 |
| 배포 연결 | publishers가 make 산출물을 GitHub, S3 같은 배포 위치로 보낸다 | publish 설정과 electron-updater 조합을 자주 사용한다 |
electron-builder는 macOS, Windows, Linux용 설치 파일과 배포 아카이브를 만들고, 코드 서명과 macOS 공증, 업데이트 게시 흐름까지 포함하는 통합 도구에 가깝다1. 반면 Electron Forge는 makers가 산출물을 만들고 publishers가 그 산출물을 배포 위치로 보내는 구조다. Forge의 publisher는 GitHub Releases, S3 버킷 같은 대상으로 결과물을 보낼 수 있다2.
어느 쪽이 "더 좋은 도구"인지는 프로젝트 성격에 따라 달라진다. 처음 Electron을 배우며 공식 흐름을 따라가고 싶다면 Forge가 이해하기 쉽다. 릴리스 파이프라인, 코드 서명, 여러 설치 형식, 자동 업데이트를 한 설정에서 강하게 통제하고 싶다면 electron-builder가 편하다. 중요한 것은 도구를 고른 뒤에도 릴리스 정책을 도구에만 맡기지 않는 것이다. 어떤 파일을 만들고, 어디에 올리고, 어떤 사용자가 언제 업데이트를 받게 할지는 프로젝트가 직접 정해야 한다.
설정 파일의 최소 뼈대
Forge를 사용한다면 forge.config.js에 maker와 publisher가 들어간다.
module.exports = {
packagerConfig: {
asar: true,
icon: './assets/icon'
},
makers: [
{ name: '@electron-forge/maker-squirrel' },
{ name: '@electron-forge/maker-dmg' },
{ name: '@electron-forge/maker-deb' }
],
publishers: [
{
name: '@electron-forge/publisher-github',
config: {
repository: { owner: 'team', name: 'work-notes' },
draft: true,
prerelease: false
}
}
]
};여기서 packagerConfig는 앱 묶음 자체의 설정이다. asar: true는 앱 소스 파일을 하나의 아카이브처럼 묶어 배포물을 정리한다. makers는 Windows 설치 파일, macOS DMG, Linux deb 같은 산출물 형식을 결정한다. 실제 프로젝트에서는 각 maker가 지원하는 운영체제와 옵션을 확인하고, 필요한 경우 플랫폼별 설정을 나누는 편이 안전하다. publishers는 만들어진 파일을 어디로 보낼지 결정한다.
electron-builder를 사용한다면 package.json의 build 필드나 electron-builder.yml 같은 별도 설정 파일에 정보를 모은다.
appId: com.example.worknotes
productName: Work Notes
artifactName: ${productName}-${version}-${os}-${arch}.${ext}
directories:
output: release
files:
- dist/**
- main/**
mac:
target:
- dmg
- zip
category: public.app-category.productivity
win:
target:
- nsis
linux:
target:
- AppImage
- deb이런 설정에서 중요한 값은 네 가지다. 앱 이름, 앱 식별자, 버전, 산출물 이름이다. 이 값들이 흔들리면 설치 경로, 업데이트 판별, 운영체제 권한, 사용자 신뢰가 함께 흔들린다. 특히 이미 배포한 뒤의 appId와 productName 변경은 생각보다 큰 영향을 준다. 운영체제는 같은 앱인지 다른 앱인지 식별자와 경로를 보고 판단하기 때문이다.
운영체제별 산출물 만들기
Windows에서는 보통 설치 프로그램을 만든다. 일반 사용자 대상 앱이라면 NSIS가 많이 쓰이고, 기업 배포 환경에서는 MSI가 요구될 수 있다. Squirrel.Windows는 업데이트 흐름과 함께 언급되는 경우가 많다. 어떤 형식을 선택하든 설치, 업데이트, 삭제를 실제 Windows 환경에서 확인해야 한다. 특히 설치 후 앱 데이터가 지워지지 않는지, 바탕화면 바로가기와 시작 메뉴 항목이 중복 생성되지 않는지 확인한다.
macOS에서는 먼저 .app 묶음이 만들어지고, 사용자가 내려받기 쉬운 형태로 DMG를 제공하는 경우가 많다. 자동 업데이트를 Squirrel.Mac 기반으로 구성한다면 업데이트용 ZIP 산출물과 메타데이터가 필요할 수 있다. macOS 배포의 핵심은 서명과 공증이다. 앱을 DMG로 감싸기 전에 앱 번들이 올바르게 서명되어야 하고, 배포 전 Apple 공증 절차를 거쳐야 한다.
Linux에서는 AppImage와 deb가 자주 쓰인다. AppImage는 단일 실행 파일에 가까워 배포가 쉽지만, 시스템 패키지 관리자와 깊게 통합되지는 않는다. deb는 Debian, Ubuntu 계열에서 설치 경험이 자연스럽지만, 배포판별 차이를 고려해야 한다. Linux는 Windows와 macOS처럼 Electron의 기본 autoUpdater가 제공하는 표준 업데이트 경험이 있는 환경이 아니다. 그래서 패키지 저장소, AppImage 업데이트, 앱 내부 알림 중 어떤 방식을 쓸지 별도로 정해야 한다.
아이콘, 식별자, 버전은 릴리스 자산이다
아이콘은 단순한 장식이 아니다. 설치 파일, 작업 표시줄, Dock, Alt-Tab 화면, 알림, 트레이에서 앱을 식별하게 해 준다. Windows는 .ico, macOS는 .icns, Linux는 보통 .png를 준비한다. 원본은 큰 크기로 관리하고, 플랫폼별 파일은 빌드 단계에서 생성하는 방식이 좋다.
앱 식별자는 더 신중하게 정해야 한다. macOS의 bundle identifier나 electron-builder의 appId는 보통 com.company.product 형태를 쓴다. 한 번 배포한 뒤 바꾸면 운영체제가 다른 앱으로 인식할 수 있고, 자동 업데이트 연결도 깨질 수 있다.
버전은 package.json의 version을 기준으로 관리하는 편이 안전하다. 1.4.2 같은 SemVer 형식을 사용하고, 이미 배포한 버전 번호는 다시 사용하지 않는다. 업데이트 서버와 설치 프로그램은 "같은 버전 번호지만 내용이 다른 파일"을 가장 싫어한다. 문제가 있으면 같은 버전을 덮어쓰지 말고 1.4.3처럼 새 버전을 낸다.
산출물 이름도 규칙을 정한다.
WorkNotes-1.4.2-mac-arm64.dmg
WorkNotes-1.4.2-win-x64.exe
WorkNotes-1.4.2-linux-x64.AppImage파일명에 제품명, 버전, 운영체제, 아키텍처를 넣으면 릴리스 페이지와 오류 보고를 관리하기 쉬워진다. "어느 사용자가 어떤 파일을 설치했는지"를 확인할 수 있어야 지원도 빨라진다.
코드 서명과 macOS 공증
코드 서명은 "이 앱이 이 개발자에 의해 만들어졌고, 배포 후 변경되지 않았다"는 사실을 운영체제에 증명하는 절차다. Electron 공식 문서는 배포할 앱은 코드 서명을 해야 하며, 서명되지 않은 앱은 Windows와 macOS에서 보안 경고나 실행 차단을 만날 수 있다고 설명한다3.
macOS 배포는 보통 두 단계다. 먼저 Developer ID 인증서로 앱을 서명한다. 그다음 Apple 공증 서비스에 앱을 제출한다. 공증은 Apple이 자동 검사로 앱이 사용자에게 위험한 동작을 하지 않는지 확인하는 절차다. 공증이 끝나면 티켓을 앱이나 DMG에 붙여 사용자가 오프라인 상태에서도 Gatekeeper 검사를 통과할 수 있게 한다.
Windows에서는 Microsoft의 최신 클라우드 기반 서명 서비스나 EV 코드 서명 인증서를 사용하는 흐름을 검토해야 한다. 예전에는 일반 Authenticode 코드 서명 인증서만으로도 "알 수 없는 게시자" 문제를 상당 부분 줄일 수 있었지만, 최신 Windows 배포 환경에서는 인증서 종류와 SmartScreen 평판의 영향이 훨씬 크다. Electron 공식 문서도 Windows 서명에서 Azure Artifact Signing이나 EV 인증서 기반 서명 흐름을 안내한다3.
서명된 앱은 게시자 정보를 표시할 수 있고, 사용자는 알 수 없는 실행 파일보다 더 많은 정보를 바탕으로 설치를 결정한다. 다만 서명만으로 모든 보안 경고가 즉시 사라진다고 기대하면 안 된다. 인증서 종류, 배포 이력, 다운로드 평판, 설치 파일 형식이 함께 영향을 준다. 인증서와 비밀번호, API 토큰은 절대 저장소에 커밋하지 말고 CI의 secret 저장소, 운영체제 키체인, 또는 서명 서비스의 안전한 저장소에 둔다. 타임스탬프도 설정해야 인증서가 만료된 뒤에도 과거에 서명한 빌드의 유효성을 설명할 수 있다.
자동 업데이트는 기능이 아니라 운영 정책이다
자동 업데이트는 "새 파일을 내려받아 바꾸는 기능"으로만 보면 위험하다. 사용자가 작업 중일 때 앱을 종료하면 데이터가 손실될 수 있고, 잘못된 버전을 전체 사용자에게 배포하면 복구가 어렵다.
Electron의 기본 autoUpdater는 macOS와 Windows 업데이트 흐름에서 사용된다. Linux에는 기본 autoUpdater 지원이 없으므로, 배포판의 패키지 관리자나 별도 업데이트 전략을 고려해야 한다. Forge를 쓰는 공개 GitHub 프로젝트라면 GitHub Releases와 update.electronjs.org를 조합할 수 있다. 공식 튜토리얼은 Forge의 GitHub publisher로 산출물을 GitHub Releases에 올리고, update-electron-app을 main 프로세스에서 호출하는 흐름을 제시한다4. 이 서비스는 macOS 또는 Windows 앱, 공개 GitHub 저장소, GitHub Releases에 올라간 빌드, 그리고 macOS의 경우 코드 서명된 빌드를 전제로 한다.
const { app } = require('electron');
if (app.isPackaged) {
require('update-electron-app')();
}이 방식은 시작점으로 좋다. update-electron-app은 기본적으로 앱 시작 시점과 이후 일정 간격으로 업데이트를 확인하고, 업데이트가 내려받아지면 사용자에게 재시작 여부를 묻는 흐름을 제공한다. 그러나 제품 단계에서는 더 많은 결정을 해야 한다.
첫째, 릴리스 서버가 필요하다. GitHub Releases, S3 같은 정적 스토리지, 자체 서버 중 어디를 사용할지 정한다. 업데이트 클라이언트는 이 서버의 feed나 메타데이터를 보고 현재 버전보다 새 버전이 있는지 판단한다. 플랫폼과 아키텍처별로 다른 파일을 제공해야 하므로 URL 구조를 처음부터 정리해야 한다.
둘째, 버전 정책을 정한다. stable, beta, nightly 채널을 나눌 것인지, 프리릴리스 버전을 일반 사용자에게 보이지 않게 할 것인지 결정한다. 모든 사용자가 같은 속도로 업데이트해야 하는 앱도 있지만, 업무용 앱은 일부 사용자에게 먼저 배포해 오류를 확인하는 staged rollout이 필요할 수 있다.
셋째, 롤백 가능성을 설계한다. 이미 설치된 앱을 과거 버전으로 자동 강등하는 방식은 안정적이지 않다. 문제가 생기면 업데이트 feed에서 문제 버전을 제거해 추가 확산을 막고, 더 높은 버전 번호의 수정 릴리스를 배포하는 방식이 일반적이다. 데이터 마이그레이션은 되돌릴 수 있게 만들거나, 최소한 업데이트 직전에 백업을 남겨야 한다.
넷째, 설치 시점을 사용자 흐름에 맞춘다. 업데이트를 백그라운드에서 내려받더라도 즉시 quitAndInstall()을 호출하면 작성 중인 노트가 사라질 수 있다. "업데이트가 준비되었습니다. 다시 시작하면 적용됩니다."처럼 사용자가 저장 후 재시작할 수 있는 흐름을 제공한다.
릴리스 전 체크리스트
배포 전에는 다음 항목을 매번 확인한다.
productName,appId,version이 의도한 값인지 확인한다.Windows, macOS, Linux 산출물 이름에 버전과 아키텍처가 포함되어 있는지 확인한다.
새 설치, 기존 버전 위에 설치, 삭제 후 재설치를 모두 테스트한다.
macOS 앱이 서명과 공증을 통과하는지 확인한다.
Windows 설치 파일이 올바른 게시자 정보로 서명되었는지 확인한다.
업데이트 서버에 잘못된 파일이 올라가지 않았는지, draft 릴리스라면 공개 전 검토했는지 확인한다.
업데이트 feed나 메타데이터가 플랫폼과 아키텍처별로 올바른 파일을 가리키는지 확인한다.
업데이트 후 사용자 데이터가 유지되는지 확인한다.
문제가 생겼을 때 배포 중단, 수정 릴리스, 사용자 공지를 어떻게 할지 정해 둔다.
패키징은 마지막에 한 번 실행하는 명령이 아니다. 앱의 이름, 신뢰, 업데이트, 복구 전략을 함께 묶는 릴리스 시스템이다. 이 기준이 잡혀 있으면 다음 장의 실전 프로젝트도 단순한 예제가 아니라 실제로 배포 가능한 데스크톱 앱에 가까워진다.
참고
이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.