Skip to content

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

pip install pyinstaller
pyinstaller --onefile myapp.py
# Output: dist/myapp.exe
pip install pyinstaller
pyinstaller --onefile myapp.py
# Output: dist/myapp
pip install pyinstaller
pyinstaller --onefile myapp.py
# Output: dist/myapp

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:

pylocket apps create --name "MyApp"

Note the APP_ID in the output. You'll use it for all platform builds.

Or use the Developer Portal: go to AppsCreate App.


Step 5: Protect Each Platform Build

Upload and protect each platform's artifact:

pylocket protect \
  --app <APP_ID> \
  --artifact dist/myapp.exe \
  --platform win-x64 \
  --python 3.12
pylocket protect \
  --app <APP_ID> \
  --artifact dist/myapp \
  --platform mac-arm64 \
  --python 3.12
pylocket protect \
  --app <APP_ID> \
  --artifact dist/myapp \
  --platform linux-x64 \
  --python 3.12

Each command returns a BUILD_ID. Protection runs in the cloud and typically takes 1–5 minutes.

Using the Portal

  1. Go to Apps → select your app → Upload Build
  2. Select the artifact file (the raw executable or zipped directory)
  3. Set the Platform dropdown (Windows, macOS, Linux)
  4. Click Upload Build
  5. 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:

mkdir -p dmg_contents
cp dist/protected/myapp dmg_contents/
cp dist/protected/_pylocket_rt.so dmg_contents/
cp dist/protected/.pylocket_manifest dmg_contents/
hdiutil create -volname "MyApp" -srcfolder dmg_contents -ov MyApp.dmg

Simplest: Create a .tar.gz:

tar -czf myapp-linux-x64.tar.gz -C dist/protected/ .

Professional: Create a .deb package with dpkg-deb or an .rpm with rpmbuild, including all three files in the installation directory.


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:

[Windows]  [macOS]  [Linux]

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:

  1. Downloads the protected app from the delivery page or your website
  2. Runs the executable — it looks and works like any normal application
  3. License prompt appears on first launch — customer enters their license key
  4. Activation contacts PyLocket's server, validates the key, and registers the device
  5. App runs normally — protected functions are decrypted on demand, transparently
  6. Offline use — after activation, the app works offline for up to 72 hours (configurable)
  7. 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