Multi-OS Publishing¶
This tutorial walks you through publishing a protected Python application for Windows, macOS, and Linux. By the end, you will have platform-specific protected builds that customers can download, activate with a single license key, and run on any supported OS.
Time required: ~30 minutes
Prerequisites¶
| Requirement | Notes |
|---|---|
| PyLocket CLI installed | pip install pylocket |
| PyLocket account | Sign up |
| Python 3.9+ | Same version used to build your app |
| Build tool | PyInstaller (recommended), cx_Freeze, or Briefcase |
| Access to each target OS | Native hardware, VMs, or CI runners |
What You'll Build¶
Your Python App
│
├── Build on Windows → myapp.exe → Protect → Protected myapp.exe + runtime
├── Build on macOS → myapp (binary) → Protect → Protected myapp + runtime
└── Build on Linux → myapp (binary) → Protect → Protected myapp + runtime
All three platform builds live under one app in PyLocket. A single license key works across all platforms.
Step 1: Understand What Gets Protected¶
PyLocket encrypts the Python bytecode inside your built executable and repacks it in the same format. The output is a working executable — not a .bin blob.
What to upload¶
Upload the raw build output from your build tool:
| Build Tool | What to Upload | File Extension |
|---|---|---|
PyInstaller --onefile |
The single executable | .exe (Windows) or binary (macOS/Linux) |
PyInstaller --onedir |
ZIP the output directory | .zip |
| cx_Freeze | ZIP the output directory | .zip |
| Briefcase (macOS) | The .app bundle directly |
.app |
| Briefcase (other) | ZIP the output directory | .zip |
python -m build |
The wheel file | .whl |
| Generic archive | ZIP or tar of your project | .zip, .tar.gz, .tgz, .tar.bz2 |
Recommended for end-user distribution
Use executables (.exe on Windows, .app on macOS) or PyInstaller onefiles when distributing to end users. These produce standalone applications that do not require Python on the customer's machine.
Supported file extensions (complete list)¶
.exe .app .zip .tar.gz .tgz .tar.bz2 .whl .egg .py .pyz .pyw
Any file extension not in this list will be rejected on upload.
Raw Python files (.py, .pyz, .pyw)
Raw Python scripts are supported but are a niche use case. The protected output requires Python on the end user's machine. See Protecting Raw Python Scripts for details.
What NOT to upload¶
Do not upload installer packages:
.dmg,.pkg(macOS installers).msi(Windows installer).deb,.rpm(Linux packages).AppImage
These are containers that wrap your executable. Upload the executable inside them, then optionally repackage the protected output into an installer afterward (see Step 7).
Protected output structure¶
Protection produces three files that must be distributed together:
| File | Purpose |
|---|---|
Protected executable (e.g., myapp.exe) |
Your application with encrypted bytecode |
_pylocket_rt.pyd (Windows) or _pylocket_rt.so (macOS/Linux) |
Native runtime that decrypts functions on demand |
.pylocket_manifest |
Encrypted metadata for license validation |
Step 2: Choose Your Build Strategy¶
| Strategy | Best For | Pros | Cons |
|---|---|---|---|
| PyInstaller per platform | Desktop apps, end users | Native executables, no Python needed on user's machine | Must build on each OS |
| Cross-platform wheel | Developer tools, CLI apps | Single artifact, works everywhere Python is installed | Requires Python on user's machine |
| ZIP archive | Internal tools, testing | Simple, no build step | Requires Python on user's machine |
This tutorial focuses on PyInstaller per platform — the most common approach for distributing to end users.
Step 3: Build Platform-Specific Artifacts¶
Option A: Build Locally on Each OS¶
Option B: CI/CD Matrix Build (Recommended)¶
Use GitHub Actions to build on all three platforms automatically:
# .github/workflows/build.yml
name: Build & Protect
on:
push:
tags: ["v*"]
jobs:
build:
strategy:
matrix:
include:
- os: windows-latest
platform: win-x64
artifact: dist/myapp.exe
- os: ubuntu-latest
platform: linux-x64
artifact: dist/myapp
- os: macos-latest
platform: mac-arm64
artifact: dist/myapp
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install pyinstaller -r requirements.txt
- run: pyinstaller --onefile myapp.py
- uses: actions/upload-artifact@v4
with:
name: build-${{ matrix.platform }}
path: ${{ matrix.artifact }}
Step 4: Register Your App¶
Create a single app in PyLocket — all platform builds live under it:
Note the APP_ID in the output. You'll use it for all platform builds.
Or use the Developer Portal: go to Apps → Create App.
Step 5: Protect Each Platform Build¶
Upload and protect each platform's artifact:
Each command returns a BUILD_ID. Protection runs in the cloud and typically takes 1–5 minutes.
Using the Portal¶
- Go to Apps → select your app → Upload Build
- Select the artifact file (the raw executable or zipped directory)
- Set the Platform dropdown (Windows, macOS, Linux)
- Click Upload Build
- Repeat for each platform
Upload the executable, not the installer
Upload myapp.exe, not myapp.msi. Upload the macOS binary, not the .dmg. PyLocket protects the Python bytecode inside the executable — it cannot process installer containers.
Step 6: Download Protected Output¶
Check build status and download the protected artifact:
# Check status
pylocket status --build <BUILD_ID>
# Download when READY
pylocket fetch --build <BUILD_ID> --out dist/protected/
The output directory contains three files:
dist/protected/
├── myapp.exe # Protected executable (or binary on macOS/Linux)
├── _pylocket_rt.pyd # Native runtime (.so on macOS/Linux)
└── .pylocket_manifest # Encrypted license metadata
All three files must be shipped together.
Step 7: Package for Distribution (Optional)¶
The protected output can be distributed as-is, or wrapped in platform-specific installers:
Simplest: Distribute the .exe directly (with the runtime files alongside it).
Professional: Create an .msi installer using WiX Toolset or Inno Setup that installs all three files together.
Simplest: ZIP the three output files and distribute directly.
Professional: Create a .dmg disk image:
Step 8: Set Up Licensing¶
License keys are platform-independent. One key works on Windows, macOS, and Linux.
Each activation on a different OS counts as a separate device toward the device limit. For example, with a 3-device license, a customer can activate on their Windows desktop, MacBook, and Linux server.
# Create a license (or use Stripe integration for automatic creation)
pylocket licenses create --app <APP_ID> --device-limit 3
See Configure Licensing for device limits, expiration, and offline grace periods.
Step 9: Distribute to Customers¶
Delivery Page¶
If you use Stripe Checkout, customers are redirected to the PyLocket delivery page after purchase. When your app has builds for multiple platforms, the delivery page shows labeled download buttons for each:
See Distribute Your App for Stripe, Gumroad, Paddle, and other storefront integrations.
Self-Hosted¶
Host the protected artifacts yourself (your website, GitHub Releases, S3, etc.) and provide per-platform download links to customers.
Step 10: The End-User Experience¶
Here's what your customer sees after purchasing:
- Downloads the protected app from the delivery page or your website
- Runs the executable — it looks and works like any normal application
- License prompt appears on first launch — customer enters their license key
- Activation contacts PyLocket's server, validates the key, and registers the device
- App runs normally — protected functions are decrypted on demand, transparently
- Offline use — after activation, the app works offline for up to 72 hours (configurable)
- Device limit — if the customer exceeds their device limit, activation is denied; you can reset devices from the portal
Step 11: Automate with CI/CD¶
Complete GitHub Actions workflow that builds, protects, and publishes for all three platforms:
# .github/workflows/release.yml
name: Release
on:
push:
tags: ["v*"]
jobs:
build:
strategy:
matrix:
include:
- os: windows-latest
platform: win-x64
artifact: dist/myapp.exe
- os: ubuntu-latest
platform: linux-x64
artifact: dist/myapp
- os: macos-latest
platform: mac-arm64
artifact: dist/myapp
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install pyinstaller pylocket -r requirements.txt
- run: pyinstaller --onefile myapp.py
# Protect
- run: |
pylocket protect \
--app ${{ secrets.PYLOCKET_APP_ID }} \
--artifact ${{ matrix.artifact }} \
--platform ${{ matrix.platform }} \
--python 3.12 \
--wait
# Fetch protected output
- run: pylocket fetch --latest --app ${{ secrets.PYLOCKET_APP_ID }} --platform ${{ matrix.platform }} --out dist/protected/
- uses: actions/upload-artifact@v4
with:
name: protected-${{ matrix.platform }}
path: dist/protected/
Troubleshooting¶
| Question | Answer |
|---|---|
Can I upload a .dmg or .msi? |
No. Upload the executable inside it. Wrap the protected output in an installer afterward. |
| Can I cross-compile? | PyInstaller does not support cross-compilation. Build on each target OS or use CI/CD runners. |
| Do I need three separate apps? | No. One app, multiple platform builds. |
| One license for all platforms? | Yes. License keys are platform-independent. Each OS activation counts as a separate device. |
| What if I only have one platform? | That's fine — upload a single build. The delivery page shows one download button. |
What does --python do? |
It must match the Python version used to build the artifact. Bytecode is version-specific. |
My build is an --onedir output |
ZIP the entire output directory and upload the .zip. |
Next Steps¶
| Topic | Link |
|---|---|
| Format-specific protection guides | PyInstaller, cx_Freeze, Briefcase, Wheel |
| Distribution strategies | Distribution Strategies |
| CI/CD integration | CI/CD Integration |
| Licensing configuration | Configure Licensing |
| Supported platforms reference | Supported Platforms |
| Marketplace distribution | Marketplace Distribution |