In the Linux world there are three universal package formats for packaging Linux applications. These three amigos are AppImage, Flatpak, and Snap. I started my packaging journey with AppImage which was the obvious choice as it comes first in the alphabet.

INFO INFO: The second beta release of Muscle Buddy so happens to include AppImage packages for x86_64 and aarch64 platforms.

But creating an AppImage that works for Python programs turns out to be a little bit involved. So I thought I would blog a bit on the topic and illustrate how one may package their Python programs to be delivered via an AppImage file.

Packaging of a Python program as complex as Muscle Buddy ended up being a trial and error process. I think the best way to illustrate the union of Python and AppImage is to start by illustrating the simplest possible case, and then move on to the case of how to package a program with more complex dependencies.

So this blog illustrates what I like to call:

The Case of the Spherical Cow

Spherical Cow

A Spherical Cow depicted by the now defunct Abstruse Goose web comic

Back when I was an university student, my Physics professor told my class the story of the Spherical Cow. The story of the Spherical Cow illustrates how physicist simplify a problem to the simplest case to begin their analysis. Once you have conquered the simplest case, you can move on to analyzing the next more complex case. This approach works well when programming and designing software as well.

HUMOR HUMOR: Milk production at a dairy farm was low, so the farmer wrote to the local university, asking for help from academia. A multidisciplinary team of professors was assembled, headed by a theoretical physicist, and two weeks of intensive on-site investigation took place. The scholars then returned to the university, notebooks crammed with data, where the task of writing the report was left to the team leader. Shortly thereafter the physicist returned to the farm, saying to the farmer, “I have the solution, but it works only in the case of spherical cows in a vacuum.”

INFO INFO: Now before we dig into this problem, I want to give a special shout-out to a project called python-appimage. This project has real promise and may help many people package their Python programs more easily. I learned a lot by studying it and appreciate the fine efforts of the developers working on this project. However I ended up making Muscle Buddy’s AppImage files by hand. Since I am accustomed to working with library and module dependencies on Linux, I just found that to be easier for me.

So what criteria does your program have to meet to qualify for the “Spherical Cow” solution?

  • Your Python program will work on stock CPython 3.6 or lower. This covers almost all up-to-date Linux systems. Most should have 3.8 or higher as of the time of writing this blog post.
  • Your Python program will not import any Python modules beyond those that are fully functional on a minimum Linux installation. For instance this means no SQLite or tkinter modules.
  • Your program does not need write access to its home or working directory.

If your program meets these criteria, your program should qualify to run in a “Spherical Cow” AppImage.

Creating a Generic AppImage Template

To begin, we should acquire appimagetool. The appimagetool utility is an AppImage file which functions as a program for building AppImages.

Download the version appropriate for your hardware and place it somewhere in your path as an executable.

I like to put it in my home directory’s bin directory and symbolically link it to a short name like so:

$ cp appimagetool-x86_64.AppImage ~/.local/bin
$ cd ~/.local/bin
$ chmod +x appimagetool-x86_64.AppImage
$ ln -s appimagetool-x86_64.AppImage appimagetool

To create an AppImage file, you need to start with an AppDir directory. AppImage technology is built on Linux’s FUSE (Filesystem in USErspace) and the SquashFS filesystem which is where your app resides. When you run an AppImage a SquashFS filesystem is mounted and then your application is bootstrapped.

The AppImage project has a pretty nice documentation site. I am only going to walk you through the process of creating a basic AppDir and if you want more detail you can read the
AppDir specification documentation here.

To begin we need to create a directory to store app files. This directory is referred to an AppDir in AppImage terminology.

$ mkdir Moo_World.AppDir
$ cd Moo_World.AppDir

Now one of the requirements are application icons. The icons need to be of the PNG file format and need to either be 128x128 or 256x256 resolution. GIMP is my preferred tool for creating these quick icons.

Your icon needs to exist in two or three places in the AppDir base directory. Since I used uppercase in the AppDir name, we should have a file called Moo_World.png, but a lowercase file is recommended to exist so we should have a moo_world.png file as well, and then .DirIcon needs to exists regardless. All three of these could be different files with different images as long as they are appropriately sized, but I am just going to make one and two symbolic links.

$ cd Moo_World.AppDir
$ ln -s Moo_World.png .DirIcon
$ ln -s Moo_World.png moo_world.png

It is recommended to create Unix like directory structure in your AppDir as a matter of convention. Your AppImage can be constructed to work with any directory structure. However if you want to keep with the standard convention you will want to create a directory structure as shown below.

$ tree -d
.
└── usr
    ├── bin
    ├── lib
    └── share
        ├── applications
        └── icons

Now lets create a Python app worthy of being a Spherical Cow in our usr/bin directory!

$ cd usr/bin
$ vi moo_world.py # create our file and save and exit
$ chmod +x moo_world.py

I created an embarrassingly simple Spherical Cow for our example.

moo_world.py source:

#!/usr/bin/env python3

print('Moo World!')

This example has no dependencies that would hang up your Linux system’s Python installation. You can make more complex programs, just as long as you keep them simple enough to not require any additional Python packages to be installed in order to run.

Now that we have our Python program, we have what we need to create the final two components of the AppDir. We will start with the .desktop file in the AppDir base directory.

Let’s create a moo_world.desktop file like so:

[Desktop Entry]
Version=1.0
Type=Application
Exec=moo_world.py
Name=Moo World
Icon=moo_world
Categories=Utility;

CAUTION CAUTION: The documentation states that the Icon must not be entered in without the .png extension. I am unclear what the .desktop file does as it does not install into the .local/share/application directory when run. But the file is a requirement. I suspect it plays a role in app stores, but I have yet to confirm this.

The last file is the AppRun file in the AppDir base directory. This is a small script that bootstraps your app.

Source for AppRun:

#!/usr/bin/env sh

HERE="$(dirname "$(readlink -f "${0}")")"
EXEC="${HERE}/usr/bin/moo_world.py"
exec "${EXEC}"

Make certain you make the file executable:

$ chmod +x AppRun

Moo_World.AppDir

The contents of the Moo_World.AppDir directory shown from a filemanager

Now to test your AppDir. If you can run AppRun and it successfully runs your Python program. You are ready to package your app.

$ ./AppRun
Moo World!

Our Spherical Cow is alive! queue maniacal laughter

Now we are ready to package our app into an AppImage!

You remember that appimagetool application I had you install earlier in this blog post? Well now you get to use it!

Run the appimagetool on the Moo_World.AppDir directory. You need to set the ARCH environment variable in order to perform your build. In this case we will set it to x86_64.

$ ARCH=x86_64 appimagetool Moo_World.AppDir/
appimagetool, continuous build (git version c247c92), build 246 built on 2025-03-10 23:33:23 UTC
Using architecture x86_64
/home/dalek/AppImage/Moo_World.AppDir should be packaged as Moo_World-x86_64.AppImage
WARNING: AppStream upstream metadata is missing, please consider creating it
         in usr/share/metainfo/moo_world.appdata.xml
         Please see https://www.freedesktop.org/software/appstream/docs/chap-Quickstart.html#sect-Quickstart-DesktopApps
         for more information or use the generator at
         https://docs.appimage.org/packaging-guide/optional/appstream.html#using-the-appstream-generator
Generating squashfs...
Downloading runtime file from https://github.com/AppImage/type2-runtime/releases/download/continuous/runtime-x86_64
Downloaded runtime binary of size 944632
Parallel mksquashfs: Using 16 processors
Creating 4.0 filesystem on Moo_World-x86_64.AppImage, block size 131072.
[===================================================================|] 4/4 100%

Exportable Squashfs 4.0 filesystem, zstd compressed, data block size 131072
	compressed data, compressed metadata, compressed fragments,
	compressed xattrs, compressed ids
	duplicates are removed
Filesystem size 40.58 Kbytes (0.04 Mbytes)
	99.15% of uncompressed filesystem size (40.93 Kbytes)
Inode table size 191 bytes (0.19 Kbytes)
	44.63% of uncompressed inode table size (428 bytes)
Directory table size 176 bytes (0.17 Kbytes)
	71.54% of uncompressed directory table size (246 bytes)
Number of duplicate files found 0
Number of inodes 13
Number of files 4
Number of fragments 1
Number of symbolic links 2
Number of device nodes 0
Number of fifo nodes 0
Number of socket nodes 0
Number of directories 7
Number of hard-links 0
Number of ids (unique uids + gids) 1
Number of uids 1
	root (0)
Number of gids 1
	root (0)
Embedding ELF...
Marking the AppImage as executable...
Embedding MD5 digest
Success

Please consider submitting your AppImage to AppImageHub, the crowd-sourced
central directory of available AppImages, by opening a pull request
at https://github.com/AppImage/appimage.github.io

This command created the file Moo_World-x86_64.AppImage with executable permissions. We can now test the file:

$ ./Moo_World-x86_64.AppImage
Moo World!

Success! Our Spherical Cow runs in our AppImage.

What just happened here is Moo_World-x86_64.AppImage mounted a FUSE SquashFS filesystem and ran the AppRun script which executed our Python program. The Python installation that was used was the local CPython installation on your Linux machine.

This give us a AppImage template we can copy and reuse for developing Cubic Cows, Tetrahedral Cows, even if you work your way up to a scary Cronenberg Cow.

CAUTION CAUTION: Another value ARCH can be set to is aarch64 for 64 bit ARM. I perform my aarch64 builds on real aarch64 hardware. I haven’t tried to build aarch64 on x86_64 so I cannot confirm or deny if cross-compiling works properly.

Summary

This blog post illustrates the simplest case of packaging a Python program in an AppImage file. Unfortunately any Python program which you feel is useful enough to package and distribute will probably be too complicated to be a Spherical Cow. However everything we did here needed to be done for a complex cow anyway. Stay tuned for a part II which will cover packaging a Python engine and modules for handling these more complicated creatures.

I hope you found this simple AppImage case helpful.

Enjoy!