Python Flatpak Packaging — Deep Dive

Build system internals

Flatpak-builder creates applications in a sandboxed environment that mirrors the target runtime. Understanding the build phases helps debug issues:

Phase 1: SDK setup

The build environment is constructed from the SDK (e.g., org.gnome.Sdk//45). This provides:

  • A complete Linux userspace
  • Development headers and tools
  • Python 3.x with pip, setuptools, and wheel
  • GCC, make, meson, cmake

Phase 2: Module building

Each module in the manifest is built sequentially. For Python modules:

{
    "name": "python3-cryptography",
    "buildsystem": "simple",
    "build-commands": [
        "pip3 install --verbose --exists-action=i --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} --no-build-isolation ."
    ],
    "sources": [
        {
            "type": "archive",
            "url": "https://files.pythonhosted.org/packages/.../cryptography-42.0.0.tar.gz",
            "sha256": "abc123..."
        }
    ]
}

Key flags explained:

  • --no-index --find-links — install from local sources only (network is disabled during build).
  • --prefix=${FLATPAK_DEST} — install to /app inside the Flatpak.
  • --no-build-isolation — use the SDK’s build tools instead of downloading them.

Phase 3: Finishing

After all modules are built, finish-args are applied to create the sandbox configuration, and the result is exported as a Flatpak bundle or repository.

Offline dependency bundling

Flatpak builds are network-isolated by design. All sources must be declared in the manifest. For Python’s dependency ecosystem, this requires careful management.

Using flatpak-pip-generator

# Install the tool
pip install flatpak-builder-tools

# Generate from a lockfile (recommended)
flatpak-pip-generator \
    --requirements-file requirements.txt \
    --output python3-deps \
    --build-isolation

# For packages with Rust/C build dependencies
flatpak-pip-generator \
    --requirements-file requirements.txt \
    --output python3-deps \
    --checker-data  # Include integrity checksums

The output is a JSON file containing module definitions with pinned URLs and SHA256 hashes:

{
    "name": "python3-deps",
    "buildsystem": "simple",
    "build-commands": [],
    "modules": [
        {
            "name": "python3-charset-normalizer",
            "buildsystem": "simple",
            "build-commands": [
                "pip3 install --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} charset-normalizer"
            ],
            "sources": [
                {
                    "type": "file",
                    "url": "https://files.pythonhosted.org/packages/.../charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
                    "sha256": "..."
                }
            ]
        }
    ]
}

Handling Rust-based Python packages

Some Python packages (cryptography, orjson, pydantic-core) use Rust for compilation. These need special handling because Rust’s cargo also downloads dependencies:

{
    "name": "python3-cryptography",
    "buildsystem": "simple",
    "build-commands": [
        "pip3 install --verbose --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} cryptography"
    ],
    "sources": [
        {
            "type": "file",
            "url": "https://files.pythonhosted.org/packages/.../cryptography-42.0.0-cp311-cp311-manylinux_2_28_x86_64.whl",
            "sha256": "..."
        }
    ]
}

Using pre-built wheels (.whl) avoids the Rust compilation issue entirely. Prefer wheels over source distributions in Flatpak manifests.

Portal integration for Python apps

Flatpak portals allow sandboxed applications to interact with the host system through D-Bus interfaces. Python apps use them through GLib/GObject bindings:

File chooser portal

from gi.repository import Gtk, Gio

def open_file(window):
    dialog = Gtk.FileChooserNative.new(
        "Open File",
        window,
        Gtk.FileChooserAction.OPEN,
        "_Open",
        "_Cancel",
    )
    # In a Flatpak, this automatically uses the portal
    # The user sees a native file picker with full filesystem access
    # But the app only gets access to the specific file chosen
    
    response = dialog.run()
    if response == Gtk.ResponseType.ACCEPT:
        path = dialog.get_filename()
        process_file(path)
    dialog.destroy()

Notification portal

from gi.repository import Gio

def send_notification(title, body):
    app = Gio.Application.get_default()
    notification = Gio.Notification.new(title)
    notification.set_body(body)
    app.send_notification(None, notification)
    # Works transparently in and out of Flatpak

Background portal (run when closed)

"finish-args": [
    "--talk-name=org.freedesktop.portal.Background"
]
from gi.repository import GLib, Gio

def request_background():
    bus = Gio.bus_get_sync(Gio.BusType.SESSION)
    proxy = Gio.DBusProxy.new_sync(
        bus, 0, None,
        "org.freedesktop.portal.Desktop",
        "/org/freedesktop/portal/desktop",
        "org.freedesktop.portal.Background",
        None,
    )
    proxy.call_sync(
        "RequestBackground",
        GLib.Variant("(sa{sv})", ("", {
            "reason": GLib.Variant("s", "Sync in background"),
            "autostart": GLib.Variant("b", True),
            "commandline": GLib.Variant("as", ["my-python-app", "--background"]),
        })),
        0, -1, None,
    )

Extension points

Flatpak extensions allow your app to be extended by third-party add-ons:

{
    "app-id": "com.example.MyEditor",
    "add-extensions": {
        "com.example.MyEditor.Plugin": {
            "directory": "plugins",
            "merge-dirs": "lib/python3.11/site-packages",
            "subdirectories": true,
            "no-autodownload": true
        }
    }
}

Plugin authors then create separate Flatpaks that install into the extension point:

{
    "id": "com.example.MyEditor.Plugin.SpellCheck",
    "branch": "stable",
    "runtime": "com.example.MyEditor",
    "build-extension": true
}

Flathub submission process

Prerequisites

  1. Your app must have a valid reverse-DNS app ID (e.g., com.github.username.AppName).
  2. AppStream metadata with screenshots.
  3. A desktop file.
  4. An SVG icon (required for Flathub).

Submission steps

# 1. Fork https://github.com/flathub/flathub on GitHub

# 2. Create a branch named after your app ID
git checkout -b com.example.MyPythonApp

# 3. Add your manifest
# com.example.MyPythonApp.json (or .yml)

# 4. Test the build
flatpak-builder --repo=repo --force-clean build-dir com.example.MyPythonApp.json

# 5. Verify the output
flatpak --user remote-add --no-gpg-verify local-repo repo
flatpak --user install local-repo com.example.MyPythonApp
flatpak run com.example.MyPythonApp

# 6. Submit a pull request to flathub/flathub

The Flathub team reviews submissions for:

  • Build reproducibility
  • Proper sandboxing (no unnecessary permissions)
  • Desktop integration quality
  • AppStream metadata completeness
  • License compliance

Automated updates

Flathub supports automated update checking via x-checker-data:

"sources": [
    {
        "type": "archive",
        "url": "https://github.com/user/repo/archive/refs/tags/v1.0.0.tar.gz",
        "sha256": "...",
        "x-checker-data": {
            "type": "json",
            "url": "https://api.github.com/repos/user/repo/releases/latest",
            "version-query": ".tag_name | sub(\"^v\"; \"\")",
            "url-query": ".tarball_url"
        }
    }
]

The Flathub bot periodically checks for new releases and automatically submits PRs to update the manifest.

CI/CD pipeline

# .github/workflows/flatpak.yml
name: Flatpak Build
on: [push, pull_request]

jobs:
  flatpak:
    runs-on: ubuntu-latest
    container:
      image: bilelmoussaoui/flatpak-github-actions:gnome-45
      options: --privileged
    
    steps:
      - uses: actions/checkout@v4
      
      - uses: flatpak/flatpak-github-actions/flatpak-builder@v6
        with:
          manifest-path: com.example.MyPythonApp.json
          bundle: my-python-app.flatpak
          cache-key: flatpak-builder-${{ github.sha }}
      
      - uses: actions/upload-artifact@v4
        with:
          name: flatpak-bundle
          path: my-python-app.flatpak

Debugging Flatpak builds

# Build with verbose output
flatpak-builder --verbose --force-clean build-dir manifest.json

# Enter the build environment
flatpak-builder --run build-dir manifest.json bash

# Inside the build environment:
python3 -c "import myapp; print(myapp.__file__)"
pip3 list
ls /app/lib/python3.11/site-packages/

# Debug the running app
flatpak run --devel com.example.MyPythonApp
# --devel includes debug tools from the SDK

# Check sandbox permissions at runtime
flatpak info --show-permissions com.example.MyPythonApp

# Override permissions for testing
flatpak override --user --filesystem=home com.example.MyPythonApp

Performance considerations

Flatpak adds minimal runtime overhead:

  • Startup: 50-100ms additional for sandbox setup.
  • Filesystem: portal-mediated file access adds ~1ms per operation.
  • Graphics: Direct GPU access through --device=dri, no performance penalty.
  • Network: No overhead once --share=network is granted.

The main cost is disk space for shared runtimes (~500 MB per runtime version), but this cost is amortized across all apps sharing that runtime.

One thing to remember: Flatpak’s combination of shared runtimes, portal-based permissions, and Flathub distribution makes it the most desktop-native way to package Python GUI applications for Linux — the upfront manifest complexity pays off with professional-grade sandboxing and cross-distribution reach.

pythonflatpakpackaginglinuxdesktop

See Also

  • Python Appimage Distribution An AppImage is like a portable app on a USB stick — download one file, double-click it, and your Python program runs on any Linux computer without installing anything.
  • Python Briefcase Native Apps Imagine a travel agent who repacks your suitcase for each country's customs — Briefcase converts your Python app into proper native packages for every platform.
  • Python Mypyc Compilation Your type hints are not just for documentation — mypyc turns them into speed boosts by compiling typed Python into fast C extensions.
  • Python Nuitka Compilation What if your Python code could run as fast as a race car instead of a bicycle? Nuitka translates Python into C to make that happen.
  • Python Pex Executables Imagine zipping your entire Python project into a single magic file that runs anywhere Python lives — that's what PEX does.