Core26: bundled Python changes

Core base snaps (e.g. core22, core24) provide a run-time environment with a minimal set of libraries that are common to many applications.

Core base snaps up to and including core24 bundle the Python interpreter alongside a selection of Python libraries. But from core26 onwards, Python packages will no longer be included. This change does not affect earlier core base snaps.

The majority of application snaps that need Python already stage Python independently. These snaps will not need to be modified.

If your snap relies on the Python interpreter provided by the core snap, and you are switching or planning to use the new core26 base and later versions, you will need to add the Python plugin to your snap.

Why are we making this change?

We continuously simplify core base snaps to remove unnecessary dependencies, improve maintainability, reduce attack surface, and keep the runtime environment minimal and predictable. In this context, core bases historically shipped some Python dependencies.

However, the recommended approach for snap creators has always been to use the Python plugin and bundle their own Python runtime inside their application snaps, including only the packages and dependencies they require. As a result, the Python interpreter and libraries provided by core snaps are often unused.

For this reason, starting with core26, the Python environment in the core base snap won’t be available at runtime.

This change better reflects how snaps are built and used today, where snap developers retain full control over their Python versions and dependencies. It also allows for the core base to be simplified. Over time, this will also allow for a reduction in the overall size of the core snaps.

How to manage the Python environment in your snap

Developers publishing snaps with Python-based parts can continue to use the Snapcraft Python plugin, which allows you to bundle the exact Python version and dependencies your application requires. Detailed guidance is available in the How-to Guide Craft a Python app

For projects that use Poetry or uv, developers should consider using the separate Snapcraft plugins built for these dependency management tools. A full list of available plugins and their usage is available in the Plugin Reference.

Impact on cloud-init

This change does not affect cloud-init usage, which will remain present and fully supported in this release.

However, since not every user requires cloud-init, a new track will be available for core26. This track will include cloud-init and the Python environment it needs.

Questions or comments?

If you have questions or concerns about this transition, please share them below.

10 Likes

I wholeheartedly support this change! Thanks for the details @ndyer

1 Like

Will the desktop extensions have a Python bundle with them?

I can see this being okay for Ubuntu Core scenarios and apps that don’t make use of Python - but in a broader desktop context I personally think that having an externally managed Python, if not by the base snap, by the extension snaps, is ideal.

Python is such as a common component generally that I feel it’s very likely there’s a lot of tools that make use of it without checking/ensuring it’s actually there. On Ubuntu itself Python is essentially a critical component of the OS not to be messed with given how much relies on it. Some applications might only use it extremely infrequently, but make assumptions that it’s there for one off jobs.

In desktop land then, that means it only takes 1 app to use Python to lose the benefits of being space efficient, whilst making it another package that people have to rebuild their snaps for for security reasons. To me it’d make sense for the Gnome/KDE/etc content snaps for Core26 to have the same Python version as Ubuntu 26.04 itself by default.

I’m not against the idea by itself and don’t know contexts around the decision making for it, but personally I feel for desktop use, there’s more to lose than gain with this change, so having the extensions compensate a little would be nice.

If I’m not mistaken, the content Snaps for the gnome and kde-neon-6 extensions basically add the Python that’s available in the repository.

So they won’t be affected by the lack of Python in the core26 Snap.

1 Like

Judging by gnome-sdk/snap/snapcraft.yaml at f72fca3a29abef1285e2f18c89f744a01754cc03 Β· ubuntu/gnome-sdk Β· GitHub and a few other lines, it looks like Python in the Gnome content snap is removed explicitly, which would make sense with it overlapping with Core; but I think should be reconsidered in Core26.

james@James-Desktop:~/squashfs-root$ unsquashfs gnome-46-2404_153.snap
james@James-Desktop:~$ cd squashfs-root/
james@James-Desktop:~/squashfs-root$ find . -name 'python'
> 
1 Like

Could it be responsible for not having python3 in PATH despite having multiple python3-* packages in build-packages?

We ended up needing this bit:

$ git show
commit fb83e3a9b2a577cc82cc07024a9986de926224f6 (HEAD -> core26)
Author: Simon Deziel <simon.deziel@canonical.com>
Date:   2026-04-22 18:08:37 -0400

    lxd: ensure `python3` is found in `PATH` to build the docs
    
    Signed-off-by: Simon Deziel <simon.deziel@canonical.com>

diff --git a/snapcraft.yaml b/snapcraft.yaml
index 6fee6156..94951a45 100644
--- a/snapcraft.yaml
+++ b/snapcraft.yaml
@@ -1411,7 +1411,7 @@ parts:
       # Not worth the effort for now.
       if [ "$(uname -m)" != "armv7l" ] && [ "$(uname -m)" != "riscv64" ]; then
         # Build the static website
-        make doc
+        PATH="/usr/bin:${PATH}" make doc
 
         # Remove unneeded bits
         rm doc/_build/objects.inv    # only objects.inv.txt is used

Will shared-memory: private: true definition still be required to manage multithreaded python ?

Thx <3

Is there a Snapcraft version target for this change so we can test it, or still to be announced ?

The correct snapcraft version is snapcraft 8.

You’ll see this new Python behaviour if you use base: core24 in your snapcraft.yaml

oHi @ndyer ,

I’m confused, I thought it was something new for core26.

I already pack python3 in stage-packages dependencies with core24.

I tried to build with core26 candidate snap and snapcraft 8.14.5 from Ubuntu 24.04 OS. Snapcraft works on python 3.12 and build the snap with python 3.14 from dependencies.

It fails to build. This problem already exists on core24 with different python versions between snapcraft/plugin and dependencies. Python plugin can’t find python in /usr/bin in the 2nd part of the build while it finds it in 1st part…

I thought it was the new improvement ; Did I miss something ?

Thanks

Build:

:: + craftctl default
:: + python3.14 -m venv /root/parts/bazarr/install
:: + PARTS_PYTHON_VENV_INTERP_PATH=/root/parts/bazarr/install/bin/python3.14
:: + /root/parts/bazarr/install/bin/pip install -U pip setuptools wheel
:: Requirement already satisfied: pip in /root/parts/bazarr/install/lib/python3.14/site-packages (25.1.1)
:: Collecting pip
::   Downloading pip-26.1-py3-none-any.whl.metadata (4.6 kB)
:: Collecting setuptools
::   Downloading setuptools-82.0.1-py3-none-any.whl.metadata (6.5 kB)
:: Collecting wheel
::   Downloading wheel-0.47.0-py3-none-any.whl.metadata (2.3 kB)
:: Collecting packaging>=24.0 (from wheel)
::   Downloading packaging-26.2-py3-none-any.whl.metadata (3.5 kB)
:: Downloading pip-26.1-py3-none-any.whl (1.8 MB)
::    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.8/1.8 MB 3.8 MB/s eta 0:00:00
:: Downloading setuptools-82.0.1-py3-none-any.whl (1.0 MB)
::    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.0/1.0 MB 3.0 MB/s eta 0:00:00
:: Downloading wheel-0.47.0-py3-none-any.whl (32 kB)
:: Downloading packaging-26.2-py3-none-any.whl (100 kB)
:: Installing collected packages: setuptools, pip, packaging, wheel
::   Attempting uninstall: pip
::     Found existing installation: pip 25.1.1
::     Uninstalling pip-25.1.1:
::       Successfully uninstalled pip-25.1.1
::
:: Successfully installed packaging-26.2 pip-26.1 setuptools-82.0.1 wheel-0.47.0
:: + /root/parts/bazarr/install/bin/pip install -U -r requirements.txt
:: WARNING: Cache entry deserialization failed, entry ignored
:: Ignoring pywin32: markers 'platform_system == "Windows"' don't match your environment
:: Requirement already satisfied: setuptools in /root/parts/bazarr/install/lib/python3.14/site-packages (from -r requirements.txt (line 1)) (82.0.1)
:: WARNING: Cache entry deserialization failed, entry ignored
:: Collecting lxml>=4.3.0 (from -r requirements.txt (line 2))
::   Downloading lxml-6.1.0-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl.metadata (4.0 kB)
:: Collecting numpy<2.4.0,>=1.12.0 (from -r requirements.txt (line 3))
::   Downloading numpy-2.3.5-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.metadata (62 kB)
:: Collecting webrtcvad-wheels>=2.0.10 (from -r requirements.txt (line 4))
::   Downloading webrtcvad_wheels-2.0.14.tar.gz (70 kB)
::   Installing build dependencies: started
::   Installing build dependencies: finished with status 'done'
::   Getting requirements to build wheel: started
::   Getting requirements to build wheel: finished with status 'done'
::   Preparing metadata (pyproject.toml): started
::   Preparing metadata (pyproject.toml): finished with status 'done'
:: Collecting Pillow>=9.0.0 (from -r requirements.txt (line 5))
::   Downloading pillow-12.2.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.metadata (8.8 kB)
:: Downloading numpy-2.3.5-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (16.6 MB)
::    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 16.6/16.6 MB 4.3 MB/s  0:00:03
:: Downloading lxml-6.1.0-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl (5.2 MB)
::    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 5.2/5.2 MB 4.5 MB/s  0:00:01
:: Downloading pillow-12.2.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (7.1 MB)
::    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 7.1/7.1 MB 4.6 MB/s  0:00:01
:: Building wheels for collected packages: webrtcvad-wheels
::   Building wheel for webrtcvad-wheels (pyproject.toml): started
::   Building wheel for webrtcvad-wheels (pyproject.toml): finished with status 'done'
::   Created wheel for webrtcvad-wheels: filename=webrtcvad_wheels-2.0.14-cp314-cp314-linux_x86_64.whl size=77820 sha256=6f6f1ce64b2cc97cd0c2b622890c05356d88478995666be5a7202e66ad039cf7
::   Stored in directory: /root/.cache/pip/wheels/34/cc/e8/c2a5f73b19d5dbc9b454ac337f4acb9c5b1c903cffabf045e4
:: Successfully built webrtcvad-wheels
:: Installing collected packages: webrtcvad-wheels, Pillow, numpy, lxml
::
:: Successfully installed Pillow-12.2.0 lxml-6.1.0 numpy-2.3.5 webrtcvad-wheels-2.0.14
:: + '[' -f setup.py ']'
:: + '[' -f pyproject.toml ']'
:: + xargs --no-run-if-empty -0 sed -i '1 s|^#\!/root/parts/bazarr/install/bin/python3.14.*$|#!/usr/bin/env python3.14|
:: + find /root/parts/bazarr/install -type f -executable -print0
:: ++ set +o
:: ++ grep errexit
:: + opts_state='set +o errexit'
:: + set +e
:: + install_dir=/root/parts/bazarr/install/usr/bin
:: + stage_dir=/root/stage/usr/bin
:: +++ readlink -f /root/parts/bazarr/install/bin/python3.14
:: ++ basename /usr/bin/python3.14
:: + basename=python3.14
:: + echo Looking for a Python interpreter called '"python3.14"' in the payload...
:: Looking for a Python interpreter called "python3.14" in the payload...
:: ++ find /root/parts/bazarr/install/usr/bin /root/stage/usr/bin -type f -executable -name python3.14 -print -quit
:: ++ true
:: + payload_python=
:: + '[' -n '' ']'
:: + echo 'Python interpreter not found in payload.'
:: Python interpreter not found in payload.
:: + symlink_target=
:: + '[' -z '' ']'
:: + echo 'No suitable Python interpreter found, giving up.'
:: No suitable Python interpreter found, giving up.
:: + exit 1
Failed to run the build script for part 'bazarr'.
Detailed information:
:: + '[' -z '' ']'
:: + echo 'No suitable Python interpreter found, giving up.'
:: No suitable Python interpreter found, giving up.
:: + exit 1

Symlink of python3 package:

ls -l /root/parts/bazarr/install/bin/python3.14
lrwxrwxrwx 1 root root /root/parts/bazarr/install/bin/python3.14 -> /usr/bin/python3.14
1 Like

You’ll see this new Python behaviour if you use base: core24 in your snapcraft.yaml

Apologies, I meant to say

You’ll see this new Python behaviour if you use base: core26 in your snapcraft.yaml

So indeed, if you are seeing an issue on core24, it is nothing to do with this change.

Edit: Also, Snapcraft 9.x is required to build stable core26 snaps.

Same as @TehAppKiller I’m having all sorts of issues with python3’s path being messed up in my base: core26 snap (LXD). Things worked well back with core24 but now under core26 I need to do all sorts of contortion to have /usr/bin added to PATH.

@pmeulengracht has had a look at the reported issue. We don’t believe it’s related to the change mentioned in this post. It’s more likely to be a snapcraft issue related to the recent change to python 3.14 (which only recently was released in core26), so we’ve raised with them.

1 Like

Thank you @ndyer. @pmeulengracht let me know if you need more information or want me to test things. Also, if there’s a bug somewhere, I’d like to subscribe to it, thanks!

Hi there,

I’m from the Snapcraft development team. I’m having trouble reproducing your problems. When Snapcraft 8.14.5 was tagged for release, the tag succeeded a simple smoke test for Python, whose project file is here: snapcraft/tests/spread/plugins/craft-parts/build-and-run-hello/python-hello/snap/snapcraft.yaml at 8.14.5 Β· canonical/snapcraft Β· GitHub

I recognize that that is core22, but if I change the same project file to instead be core24, pack that, and install it, it succeeds (although with a runtime warning because it improperly includes GNOME stuff, but that’s not related to this problem).

For edge Snapcraft, where core26 support is being developed, we have this test: https://github.com/canonical/snapcraft/blob/main/tests/spread/_common/python-hello/strict/snap/snapcraft.yaml

Likewise, from what I can tell, this appears to build happily on edge Snapcraft. Due to the degraded state of package repositories, I can’t verify that it works specifically as of writing, but it worked when it was added (a9a5b47) and there haven’t been any changes up until today (05c7c6c) that should’ve changed that behavior.

To help troubleshoot,

  • Does your project look like either of these? Is there something special about your project?
  • There have been issues in the past that gave error messages like yours and often needed workaround scripts, but I’m not aware of any such active issues. Is it possible that your project file contains an old, now-unneeded workaround that could be modifying Python at build time?
  • Could you possibly share the project file, or even better, the whole repository?

With the command: bin/python $SNAP/bin/hello, you are effectively sidestepping many of the issues stemming from not having python3 in the core26 base.

Some problems are due to the shebang used by various scripts

  1. #!/usr/bin/python3 does not exist 2.#!/usr/bin/env python3 finds a symlink pointing to the vendored python3.14 file which then is then prevented from executing by Apparmor

This means you have to pull a layout trick (this adds overhead when you invoke your commands though):

layout:
  /usr/bin/python3:
    # XXX: Apparmor follows symlinks so pointing to `$SNAP/bin/python3`
    # causes denials when the snap tries to execute Python scripts.
    # Instead, point directly to the actual interpreter.
    bind-file: $SNAP/bin/python3.14

Or you patch all the Python scripts to use a shebang like #!/snap/<SNAP>/current/bin/python3.14.

oHi @bepri ,

In core24, declaring python3 as a stage-package dependency was sufficient for an app using the same python version between core24 and snapcraft/plugin (cf. code at end of message).

Now with core26, it works using the trick of forcing the declaration of stage-packages for python3 and python3-venv before (with β€˜after’ tag for the python-app-part) . But from my experience it’s not common to have to do so for stage-packages ; and I don’t think it was documented for core24 using different python versions, was it ?

Now, It builds with ELF warnings and does NOT run.

It seems every libs and paths have an ELF warnings at linter ; e.g.:

- classic: usr/sbin/fstab-decode: ELF rpath should be set to '$ORIGIN/../lib/x86_64-linux-gnu'. (https://documentation.ubuntu.com/snapcraft/stable/how-to/debugging/use-the-classic-linter)
- classic: usr/sbin/killall5: ELF interpreter should be set to '/snap/bazarr-tak/current/usr/lib64/ld-linux-x86-64.so.2'. (https://documentation.ubuntu.com/snapcraft/stable/how-to/debugging/use-the-classic-linter)

I don’t have time to test more today.

It seems to work also with python3 and python3-venv in β€˜stage-packages’ of the python-app-part today(?).

It does not work as a simple dependency not β€œbefore” the app part (= as stage-packages in a separate nil plugin part):

deps:
    plugin: nil
    source: .
    stage-packages:
      - python3

Thx <3

@TehAppKiller,

Those linter warnings should be resolved in Snapcraft 9 (Unwanted staged dependencies when building with a `core26` base Β· Issue #6148 Β· canonical/snapcraft Β· GitHub). Snapcraft 8.14 was staging all deps for core26 snaps, including those that were already in the base snap.

Now with core26, it works using the trick of forcing the declaration of stage-packages for python3 and python3-venv before (with β€˜after’ tag for the python-app-part) . But from my experience it’s not common to have to do so for stage-packages ; and I don’t think it was documented for core24 using different python versions, was it ?

Correct, this wasn’t documented for core24. There’s no reason you can’t just declare those stage-packages in your python-app-part. The reason we’re recommending a standalone part that builds first is to make it easier to copy/paste into a project file and easier to remove in the future if we provide a Python content snap.

@sdeziel,

Regarding this bit:

-        make doc
+        PATH="/usr/bin:${PATH}" make doc

In part, that’s due to craft-parts appending the build-environment before the path (source), so it tries to use the staged python instead of the system’s python (installed by build-packages). I’m still not confident why this extra change was needed, but I suspect a newly staged package (python3-venv?) is now matching a shebang (i.e. something path shadowing /usr/bin/python(3?)).

As far as your shebang changes go, I think updating the shebangs is a valid workaround (similar to what craft-parts does for the venv).

The layout trick is clever. You should be able to get away with a symlink too, which is supposedly cheaper for snapd than a bind mount.

Either way, we need to update the core26 migration guide and python plugin docs to make it clear that scripts with hardcoded /usr/bin/python3 shebangs will break. We should also add a test to snapcraft that’s a bit more representative of larger projects with Python scripts, as the current tests are too simple.

@mr_cal thanks that’s useful information! Re the layout trick, I’ll try the symlink and see if it works.

I’ll also see if some of the parts in the LXD snap could be using the python plugin in a similar fashion as https://github.com/canonical/snapcraft/blob/main/tests/spread/_common/python-symlinks/provisioned-from-another-part/snapcraft.yaml