Deployment — Building & Packaging for Production
1. Build overview
Architecture
Production Build
├── Frontend (React + Vite)
│ ├── npm run build → dist/
│ ├── Static HTML/JS/CSS
│ └── CSP-hardened (no unsafe-eval)
├── Electron Shell
│ ├── npm run build → dist/main/
│ ├── Preload script (IPC bridge)
│ └── Main process (window, Python bridge)
└── Backend (Python + Models)
├── Bundled in extraResources/python/
├── requirements.txt
└── All .py files
Key difference from dev:
- Frontend served from
file://(no Vite dev server) - Backend spawned by Electron (no manual
python main.py) - Models downloaded on first run (not bundled, too large)
2. Prerequisites
| Tool | Version | Purpose |
|---|---|---|
| Node.js | 18+ | Frontend build, Electron |
| Python | 3.11+ | Backend runtime |
| npm | 9+ | Dependency management |
| Git | Any | Version control |
Platform-specific:
- Windows: Visual Studio Build Tools (for node-gyp)
- macOS: Xcode Command Line Tools
- Linux:
gcc,g++,make
3. Building the frontend
Step 1: Install dependencies
bashcd frontend
npm install
Step 2: Build
bashcd frontend
npm run build
What it does:
- TypeScript compilation (
tsc) - Vite production build (
vite build) - CSP injection (production policy, no
unsafe-inlinescripts) - CSP validation (
scripts/check-csp.mjs) — fails on violation - Output →
frontend/dist/
Output structure
frontend/dist/
├── index.html ← CSP <meta> tag injected
├── assets/
│ ├── index-[hash].js
│ ├── index-[hash].css
│ └── ...
└── ...
Verification
bashcd frontend
npm run check:csp
Expected output:
✓ CSP meta tag found in dist/index.html
✓ Production CSP does not contain 'unsafe-eval'
✓ Production CSP does not contain 'unsafe-inline' in script-src
4. Building Electron
Step 1: Install dependencies
bashcd frontend/electron
npm install
Step 2: Build main process
bashcd frontend/electron
npm run build
What it does:
- TypeScript compilation (
tsc) - Path alias resolution (
tsc-alias) - Electron security check (
scripts/check-electron-security.mjs) — fails on violation - Output →
frontend/electron/dist/main/
Output structure
frontend/electron/dist/main/
├── index.js ← Main process entry
├── preload.js ← IPC bridge
├── window.js ← BrowserWindow creation
├── python-bridge.js ← Backend lifecycle
└── ...
Verification
bashcd frontend/electron
npm run check:security
Expected output:
✓ All BrowserWindows use sandbox: true
✓ All BrowserWindows use contextIsolation: true
✓ All BrowserWindows use nodeIntegration: false
✓ No --no-sandbox flag found
5. Full build (frontend + Electron)
Single command
bash# From project root
npm run build
What it does:
npm run build:frontend→cd frontend && npm run buildnpm run build:electron→cd frontend/electron && npm run build
Both security checks run automatically — build fails on violations.
6. Packaging
Packaging creates platform-specific installers using electron-builder.
Package configuration
File: frontend/electron/package.json → "build" section
json{
"build": {
"appId": "com.lawyerassistant.app",
"productName": "Lawyer Assistant",
"directories": {
"output": "dist-electron"
},
"files": [
"dist/**/*",
"../dist/**/*",
"!**/node_modules/**"
],
"extraResources": [
{
"from": "../../backend",
"to": "python",
"filter": [
"**/*.py",
"requirements.txt"
]
}
],
"win": {
"target": "nsis",
"icon": "../../assets/icon.ico"
},
"mac": {
"target": "dmg",
"icon": "../../assets/icon.icns"
},
"linux": {
"target": "AppImage",
"icon": "../../assets/icon.png"
}
}
}
Package for all platforms
bash# From project root
npm run package
Creates installers for current platform only.
Platform-specific packaging
Windows:
bashnpm run package:win
macOS:
bashnpm run package:mac
Linux:
bashnpm run package:linux
Output
frontend/electron/dist-electron/
├── win-unpacked/ ← Windows executable (unpacked)
├── Lawyer Assistant Setup 1.0.0.exe ← Windows installer (NSIS)
├── mac/ ← macOS app bundle
├── Lawyer Assistant-1.0.0.dmg ← macOS installer
├── linux-unpacked/ ← Linux executable (unpacked)
└── Lawyer Assistant-1.0.0.AppImage ← Linux installer
7. Backend bundling
What's bundled
The backend is bundled as extraResources/python/:
extraResources/python/
├── backend/
│ ├── main.py
│ ├── legal_retrieval/
│ │ ├── __init__.py
│ │ ├── config.py
│ │ ├── embedder.py
│ │ └── ...
│ ├── scripts/
│ └── tests/ (optional, can be excluded)
├── requirements.txt
└── (no .venv — created on first run)
Installation on first run
When the user launches the packaged app:
- Electron checks for
.venvin app data directory - If missing:
- Runs
python -m venv .venv - Runs
.venv\Scripts\pip install -r requirements.txt - Downloads models (BGE-M3, BGE-Reranker) on first query
- Runs
- Starts backend:
.venv\Scripts\python main.py
User sees:
- Progress spinner during first-time setup
- "Downloading models..." on first query
8. Code signing (optional)
Why sign?
- Windows: Avoids "Unknown publisher" warning
- macOS: Required for Gatekeeper (App Store distribution)
- Linux: Optional
Windows code signing
Prerequisites:
- Code signing certificate (
.pfxfile) - Password for certificate
Configuration:
json{
"build": {
"win": {
"certificateFile": "path/to/certificate.pfx",
"certificatePassword": "${env.WIN_CSC_PASSWORD}",
"signAndEditExecutable": true
}
}
}
Package with signing:
bashexport WIN_CSC_PASSWORD=your_password
npm run package:win
macOS code signing
Prerequisites:
- Apple Developer account
- Developer ID Application certificate
Configuration:
json{
"build": {
"mac": {
"identity": "Developer ID Application: Your Name (TEAM_ID)",
"hardenedRuntime": true,
"gatekeeperAssess": false,
"entitlements": "entitlements.mac.plist"
}
}
}
Package with signing:
bashnpm run package:mac
9. Distribution
Option 1: GitHub Releases
bash# Build for all platforms (requires Mac/Windows/Linux machines)
npm run package:win
npm run package:mac
npm run package:linux
# Upload to GitHub Releases
gh release create v1.0.0 \
frontend/electron/dist-electron/*.exe \
frontend/electron/dist-electron/*.dmg \
frontend/electron/dist-electron/*.AppImage \
--title "v1.0.0" \
--notes "Release notes here"
Option 2: Direct download
Host installers on your website:
https://yoursite.com/downloads/
├── Lawyer-Assistant-1.0.0-win.exe
├── Lawyer-Assistant-1.0.0-mac.dmg
└── Lawyer-Assistant-1.0.0-linux.AppImage
Option 3: Auto-updater
Use electron-updater for automatic updates:
Installation:
bashcd frontend/electron
npm install electron-updater
Configuration:
typescript// frontend/electron/src/main/index.ts
import { autoUpdater } from 'electron-updater';
app.on('ready', () => {
autoUpdater.checkForUpdatesAndNotify();
});
Requires:
- Signed builds
- Update server (GitHub Releases supported out-of-the-box)
10. Platform-specific considerations
Windows
Installer type: NSIS (default) or Squirrel
Installation path: C:\Users\<username>\AppData\Local\Lawyer Assistant\
Auto-start: Optional (add registry key)
Portable version:
json{
"build": {
"win": {
"target": ["nsis", "portable"]
}
}
}
macOS
Installer type: DMG (default) or PKG
Installation path: /Applications/Lawyer Assistant.app
Gatekeeper: Requires code signing for Catalina+
Notarization: Required for macOS 10.15+ (Apple notary service)
bash# Notarize after signing
xcrun notarytool submit \
"Lawyer Assistant-1.0.0.dmg" \
--apple-id "your@email.com" \
--team-id "TEAM_ID" \
--password "app-specific-password" \
--wait
Linux
Installer types:
- AppImage (portable, no installation)
- Snap (Ubuntu Software Center)
- deb (Debian/Ubuntu)
- rpm (Fedora/Red Hat)
Multiple targets:
json{
"build": {
"linux": {
"target": ["AppImage", "deb", "rpm"]
}
}
}
11. Build optimization
Reduce bundle size
Frontend:
- Tree-shaking (Vite does this automatically)
- Remove unused dependencies
- Lazy load components
Backend:
- Exclude
tests/from bundle:
json {
"extraResources": [{
"from": "../../backend",
"to": "python",
"filter": ["**/*.py", "requirements.txt", "!**/tests/**"]
}]
}
Models:
- Don't bundle models (14GB too large)
- Download on first run (current approach)
Faster builds
Skip type checking (not recommended for production):
bashnpm run build -- --skipTypeCheck
Parallel builds:
bash# Build frontend and Electron in parallel
npm run build:frontend & npm run build:electron
Cache Electron binaries:
bashexport ELECTRON_CACHE=~/.electron-cache
12. CI/CD for releases
GitHub Actions workflow
File: .github/workflows/release.yml
yamlname: Release
on:
push:
tags:
- 'v*'
jobs:
release:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [windows-latest, macos-latest, ubuntu-latest]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
- uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install dependencies
run: npm install
- name: Build
run: npm run build
- name: Package
run: npm run package
- name: Upload artifacts
uses: actions/upload-artifact@v3
with:
name: ${{ matrix.os }}-build
path: frontend/electron/dist-electron/*
- name: Create Release
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/')
with:
files: frontend/electron/dist-electron/*
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Trigger release
bashgit tag -a v1.0.0 -m "Release 1.0.0"
git push origin v1.0.0
GitHub Actions builds for all platforms and creates a release automatically.
13. Testing the packaged app
Before distributing
- Smoke test:
bash # Windows
"frontend/electron/dist-electron/win-unpacked/Lawyer Assistant.exe"
# macOS
open "frontend/electron/dist-electron/mac/Lawyer Assistant.app"
# Linux
./frontend/electron/dist-electron/linux-unpacked/lawyer-assistant
- Verify:
- App starts without errors
- Backend spawns successfully
- Models download on first query
- Chat works end-to-end
- File upload works
- Workspace selection works
- Fresh user test:
- Test on a machine without dev tools installed
- Verify first-time setup (venv creation, model download)
- Check installer user experience
14. Troubleshooting packaging
| Issue | Cause | Fix |
|---|---|---|
| Build fails | Security check failed | Run npm run check:security |
| Large bundle | Models bundled | Exclude models/ from extraResources |
| Backend won't start | Python not found | Bundle Python runtime (pyinstaller) |
| Missing dependencies | requirements.txt outdated | Update requirements |
| Icon not showing | Wrong icon format | Convert to .ico (Win), .icns (Mac) |
| "Unknown publisher" | Not code signed | Sign the executable |
| macOS Gatekeeper blocks | Not notarized | Notarize the DMG |
15. Version management
Update version
File: package.json (root, frontend, electron)
Update in all three:
json{
"version": "1.0.1"
}
File: backend/legal_retrieval/__init__.py
python__version__ = "1.0.1"
Automated version bump
bash# Patch version (1.0.0 → 1.0.1)
npm version patch
# Minor version (1.0.0 → 1.1.0)
npm version minor
# Major version (1.0.0 → 2.0.0)
npm version major
Updates package.json and creates a git tag.
16. Production environment variables
Set these in the packaged app environment:
| Env Var | Production Value | Purpose |
|---|---|---|
NODE_ENV | production | Vite production mode |
PLR_LOG_LEVEL | WARNING | Less verbose logs |
PLR_GPU_MANAGER_ENABLED | 1 | Enable GPU management |
ELECTRON_DISABLE_SECURITY_WARNINGS | true | Hide dev warnings |
Set in Electron main process:
typescriptprocess.env.NODE_ENV = 'production';
process.env.PLR_LOG_LEVEL = 'WARNING';
17. Related documentation
| Doc | Coverage |
|---|---|
SECURITY.md | Security hardening, CSP, sandboxing |
CONFIGURATION.md | Environment variables, settings |
TROUBLESHOOTING.md | Common failures |
DEVELOPMENT.md | Dev environment, debugging |
FRONTEND.md | Frontend build process |
ARCHITECTURE.md | System overview |