Python Snap Packaging — Deep Dive
Snap filesystem layout
When a snap is installed, its contents are mounted at /snap/<name>/current/. The internal layout follows a convention:
/snap/my-app/current/
├── bin/ # Entry point scripts
│ └── my-app
├── lib/ # Python and shared libraries
│ └── python3.10/
│ └── site-packages/
├── usr/ # System libraries bundled from the base
├── meta/ # Snap metadata
│ ├── snap.yaml # Generated from snapcraft.yaml
│ └── gui/
│ └── my-app.desktop
└── snap/
└── command-chain/ # Launch wrappers
The snap runs with several environment variables set:
SNAP=/snap/my-app/current # Snap mount point
SNAP_DATA=/var/snap/my-app/current # Writable data (persists across updates)
SNAP_USER_DATA=~/snap/my-app/current # Per-user writable data
SNAP_COMMON=/var/snap/my-app/common # Data shared across versions
Your Python code should use these paths for file storage:
import os
def get_data_dir() -> str:
"""Get writable data directory, works both snapped and unsnapped."""
snap_data = os.environ.get("SNAP_USER_DATA")
if snap_data:
return snap_data
return os.path.expanduser("~/.my-app")
Multi-part builds
Complex Python applications often need system libraries compiled from source. Snapcraft’s multi-part system handles this:
parts:
# Part 1: Build system dependencies
libgeos:
plugin: cmake
source: https://github.com/libgeos/geos/archive/refs/tags/3.12.0.tar.gz
cmake-parameters:
- -DCMAKE_INSTALL_PREFIX=/usr
build-packages:
- g++
- make
# Part 2: Python application (depends on libgeos)
my-gis-app:
after: [libgeos] # Build order dependency
plugin: python
source: .
python-packages:
- shapely # Links against libgeos
- geopandas
- click
build-packages:
- libgeos-dev # Needed at build time
stage-packages:
- libgeos-c1v5 # Needed at runtime
The after keyword ensures libgeos is built and staged before the Python part tries to compile Shapely.
Handling C extensions
Python packages with C extensions (NumPy, pandas, cryptography, etc.) need build tools and system libraries:
parts:
my-data-app:
plugin: python
source: .
python-packages:
- numpy
- pandas
- scipy
- scikit-learn
build-packages:
- gcc
- g++
- gfortran
- libopenblas-dev
- liblapack-dev
- pkg-config
stage-packages:
- libopenblas0
- liblapack3
- libgfortran5
The distinction matters:
build-packages— installed during build, not included in the final snap.stage-packages— included in the final snap, available at runtime.
For packages that provide pre-built wheels, you can skip build dependencies:
parts:
my-app:
plugin: python
source: .
python-packages:
- numpy
# Use manylinux wheels — no build tools needed
python-constraints:
- constraints.txt # Pin to known-good wheel versions
Daemon services
Snaps can run Python applications as system services:
apps:
my-service:
command: bin/my-service
daemon: simple
restart-condition: always
restart-delay: 5s
plugs:
- network
- network-bind
environment:
PYTHONUNBUFFERED: "1"
LOG_LEVEL: "INFO"
my-worker:
command: bin/celery-worker
daemon: simple
restart-condition: on-failure
after:
- my-service # Start after the main service
plugs:
- network
Managing the daemon:
# The service starts automatically on install
sudo snap install my-app
# Service management
sudo snap start my-app.my-service
sudo snap stop my-app.my-service
sudo snap restart my-app.my-service
sudo snap logs my-app.my-service
sudo snap logs my-app.my-service -n 100 # Last 100 lines
Desktop application integration
For Python GUI applications (Tkinter, PyQt, GTK):
apps:
my-gui-app:
command: bin/my-gui-app
extensions:
- gnome # GNOME desktop integration
plugs:
- desktop
- desktop-legacy
- wayland
- x11
- opengl
parts:
my-gui-app:
plugin: python
source: .
python-packages:
- PyQt5
stage-packages:
- libqt5gui5
- libqt5widgets5
Desktop file for app launcher integration:
# snap/gui/my-gui-app.desktop
[Desktop Entry]
Type=Application
Name=My GUI App
Icon=${SNAP}/meta/gui/icon.png
Exec=my-gui-app
Categories=Utility;
The extensions field is powerful — gnome automatically sets up GTK themes, fonts, cursor themes, and desktop portal integration.
CI/CD automated publishing
GitHub Actions
name: Build and Publish Snap
on:
push:
tags: ['v*']
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: snapcore/action-build@v1
id: build
- uses: snapcore/action-publish@v1
env:
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAP_TOKEN }}
with:
snap: ${{ steps.build.outputs.snap }}
release: stable
# Test before publishing
test:
runs-on: ubuntu-latest
needs: build
steps:
- name: Install snap
run: |
sudo snap install --dangerous ${{ needs.build.outputs.snap }}
my-python-app --self-test
Generate the store credentials:
snapcraft export-login --snaps my-python-app --channels stable,candidate credentials.txt
# Store the contents as a GitHub secret
Launchpad builds
Canonical’s Launchpad can build snaps automatically from GitHub:
- Connect your GitHub repo to Launchpad.
- Launchpad detects
snapcraft.yamlchanges. - Builds the snap on Canonical’s infrastructure (supports ARM, RISC-V, etc.).
- Publishes directly to the Snap Store.
This is the easiest path for multi-architecture builds — Launchpad has native ARM builders, no cross-compilation needed.
Advanced snapcraft.yaml patterns
Python version override
parts:
my-app:
plugin: python
source: .
build-environment:
- PARTS_PYTHON_INTERPRETER: python3.11
build-packages:
- python3.11-dev
- python3.11-venv
stage-packages:
- python3.11-minimal
- libpython3.11-stdlib
Startup optimization
apps:
my-app:
command: bin/my-app
command-chain:
- snap/command-chain/snapcraft-runner # Standard wrapper
environment:
# Disable Python's site module for faster startup
PYTHONNOUSERSITE: "1"
# Pre-compiled bytecode
PYTHONDONTWRITEBYTECODE: "1"
Build caching with snapcraft
# Enable build caching
snapcraft --use-lxd # LXD provides better caching than Multipass
# Clean and rebuild specific parts
snapcraft clean my-app
snapcraft
Size optimization
Snaps can be large because they bundle system libraries. Strategies to reduce size:
parts:
my-app:
plugin: python
source: .
# Only include what you need from the base
stage:
- -usr/share/doc/* # Remove documentation
- -usr/share/man/* # Remove man pages
- -usr/share/locale/* # Remove unused locales
organize:
# Restructure files if needed
usr/lib/python3/dist-packages/*: lib/python3.10/site-packages/
Typical snap sizes for Python applications:
| Content | Approximate size |
|---|---|
| Minimal CLI (click + requests) | 15-25 MB |
| Web app (Flask + SQLAlchemy) | 30-50 MB |
| Data science (numpy + pandas) | 80-120 MB |
| GUI app (PyQt5 + GNOME) | 100-200 MB |
Debugging snaps
# Run the snap in devmode for debugging
sudo snap install my-app --devmode
# Check confinement violations
sudo journalctl -t audit | grep my-app
# Enter the snap's environment
snap run --shell my-app
# Now you're inside the snap's filesystem namespace
# Check snap connections (interfaces)
snap connections my-app
# Manually connect a plug
sudo snap connect my-app:removable-media
For development iteration:
# Install from local file without store
sudo snap install my-python-app_1.0.0_amd64.snap --dangerous --devmode
# Watch logs
sudo snap logs my-app -f
One thing to remember: Snaps give Python applications a complete Linux distribution pipeline — from sandboxed packaging with the Python plugin, through multi-architecture CI builds on Launchpad, to automatic updates from the Snap Store — making it the most integrated way to ship Python software to Linux desktops and servers.
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 Flatpak Packaging Flatpak wraps your Python app in a safe bubble that works on every Linux system — like a snow globe that keeps your program perfect inside.
- 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.