How to make truly portable AppImages that work on any linux system.


Index



Quick Start Guide

TL;DR: Use quick-sharun.sh to bundle your application with all its dependencies into a truly portable AppImage that works on any Linux system.


| Back to Index | | - |


Prerequisites

You’ll need:


| Back to Index | | - |


Basic workflow

Creating an AppImage with quick-sharun involves these steps:

  1. Install your application and its dependencies on your build system
  2. Download quick-sharun.sh from this repository
  3. Set environment variables to configure quick-sharun
  4. Run quick-sharun with your application’s binary (and libraries) path to deploy.
  5. Generate the AppImage with --make-appimage flag

That’s it! The script will:


| Back to Index | | - |


Step-by-step example

Let’s create an AppImage for a simple application. Here’s a minimal example:

#!/bin/sh
set -eux

ARCH="$(uname -m)"
SHARUN="https://raw.githubusercontent.com/pkgforge-dev/Anylinux-AppImages/refs/heads/main/useful-tools/quick-sharun.sh"

# Configure the AppImage
export ICON=/usr/share/icons/hicolor/256x256/apps/myapp.png
export DESKTOP=/usr/share/applications/myapp.desktop
export OUTPATH=./dist
export OUTNAME=myapp-"$ARCH".AppImage

# Install your application (example using pacman)
pacman -Syu --noconfirm base-devel wget myapp

# Download and run quick-sharun
wget "$SHARUN" -O ./quick-sharun
chmod +x ./quick-sharun

# Bundle the application
./quick-sharun /usr/bin/myapp

# Create the AppImage
./quick-sharun --make-appimage

Using debloated packages (smaller AppImages):

EXTRA_PACKAGES="https://raw.githubusercontent.com/pkgforge-dev/Anylinux-AppImages/refs/heads/main/useful-tools/get-debloated-pkgs.sh"

wget "$EXTRA_PACKAGES" -O ./get-debloated-pkgs.sh
chmod +x ./get-debloated-pkgs.sh

# Installs a debloated MESA, vulkan, Qt, GTK, libicudata, and more
./get-debloated-pkgs.sh --add-mesa --prefer-nano

# Some appsm might require these as well
./get-debloated-pkgs.sh --add-common --prefer-nano ffmpeg-mini intel-media-driver-mini

| Back to Index | | - |


Using hooks

Hooks are scripts that solve common problems automatically. Add them using the ADD_HOOKS variable:

export ADD_HOOKS="self-updater.bg.hook:fix-namespaces.hook"
./quick-sharun /usr/bin/myapp

Available hooks:

See all hooks in useful-tools/hooks/


| Back to Index | | - |


Available environment variables

Basic configuration:

Deployment options:

Library handling:

Hooks:


| Back to Index | | - |


Understanding the approach

This section explains the technical details and philosophy behind these AppImages. If you just want to create AppImages, the Quick Start Guide above is all you need.


| Back to Index | | - |


The problem

For a long time the suggested practice to make AppImages has been to bundle most of the libraries an application needs but not all like libc, dynamic linker, and several more mentioned in the exclude list

This approach has two big issues:

And the future stability isn’t that great either, because glibc still sometimes breaks userspace with updates.


| Back to Index | | - |


The solution

This is the solution, truly portable application bundles that have everything they need.


| Back to Index | | - |


How does it work?

Note: This section explains the technical implementation details. The quick-sharun script and sharun tool handle all of this automatically, so you don’t need to do any of this manually. This is here for educational purposes.

  1. First issue to overcome:

Since we are going to bundle our own libc, it means we cannot use the host dynamic linker even, which means we have to bundle our own ld-linux/musl.so and this has a problem, we cannot simply patch out binaries to use the bundled interpreter like patchelf --set-interpreter '$ORIGIN/ld-linux.so' because that $ORIGIN resolution is done by the interpreter itself.

We can have a relative interpreter like ./ld-linux.so, the problem with this though is that we need to change the current working directory to that location for this to work. In other words, for AppImages, the current working directory will change to the random mountpoint of the AppImage and this is a problem if your application is a terminal emulator, that opens at the current working directory for example.

Instead we have to run the dynamic linker first, and then give it the binary we want to launch, which is possible, so our AppRun will look like this instead:

#!/bin/sh
CURRENTDIR="$(readlink -f "$(dirname "$0")")"

exec "$CURRENTDIR"/ld-linux-x86-64.so.2 "$CURRENTDIR"/bin/app "$@"

However this has a small issue that /proc/self/exe will be ld-linux-x86-64.so.2 instead of the name of the binary we launched. For most applications, this isn’t an issue, but when it is an issue, it is quite a big one. Sharun fixes this problem (see below), so we will continue with this approach to explain the rest.

  1. Second issue to overcome:

Now that we have our own dynamic linker, how do we tell it that we can to use all the libraries we have in our own lib directory?

#!/bin/sh
CURRENTDIR="$(readlink -f "$(dirname "$0")")"

exec "$CURRENTDIR"/ld-linux-x86-64.so.2 \
	--library-path "$CURRENTDIR"/lib \
	"$CURRENTDIR"/bin/app "$@"

We need to bundle the libraries and dynamic linker and we are almost good to go! However, to be fully ready, we need to fix the following issues below… Bundling all the needed libraries isn’t as easy as just running ldd + cp, so we need some more robust solution. Sharun handles this automatically (see below).

  1. Third issue to overcome:

Lets make our application relocatable. Thankfully this is already possible with almost all applications, I often see developers adding exceptions to their applications to make them portable, but they are rarely needed at all, because we already have the XDG Base dir specification that helps a ton here: https://specifications.freedesktop.org/basedir-spec/latest/

Instead of hardcoding your application to look for files in /usr/share, you need to check XDG_DATA_DIRS, which very likely your application already does since common libraries already follow the specification.

Then in our AppRun we include our share directory in XDG_DATA_DIRS, issue solved ✅

Same way, the dependencies we bundle will almost always have means to make relocatable any support plugin/support file they need, just to give a few examples:

And many many more!

But isn’t this a lot of work to find and set all the env variables that my application needs? Yes it is

  1. Fourth issue to overcome, I don’t want to do any of this that’s a lot of work.

| Back to Index | | - |


Sharun

There is a solution for this, made by @VHSGunzo called sharun:

https://github.com/VHSgunzo/sharun

Any application made with sharun ends up being able to work on any linux distro, be it ubuntu 14.04, musl distros and even directly in NixOS without any wrapper (non FHS environment).


| Back to Index | | - |


Further considerations


Isn’t this very bloated?

Not really, if your application isn’t hardware accelerated, bundling all the libraries will usually only increase the size of the application by less than 6 MiB.

UPDATE: We are actually now able to build mesa without linking to LLVM, so AppImages are even smaller as result, fully hardware accelerated Qt/GTK apps can be made while being less than 35 MiB in the final size.

For applications that are hardware accelerated, there is the problem that mesa links to libLLVM.so, which is a huge +130 MiB library that’s used for a lot of things. Distros by default build it with support for the following:

AArch64
AMDGPU
ARM
AVR
BPF
Hexagon
Lanai
LoongArch
Mips
MSP430
NVPTX
PowerPC
RISCV
Sparc
SystemZ
VE
WebAssembly
X86
XCore

When for most applications you only need llvm to support AMDGPU and X86/AArch64.

We already make such version of llvm here: https://github.com/pkgforge-dev/archlinux-pkgs-debloated which reduces the size of libLLVM.so down to 66 MiB.

Such package and other debloated packages we have are used by Goverlay, which results a 50 MiB AppImage that works on any linux system, which is surprisingly small considering this application bundles Qt and mesa (vulkan) among other things.


| Back to Index | | - |


What about nvidia?

Nvidia releases its proprietary driver as a binary blob that is already widely compatible on its own, it’s only requirement is a new enough version of glibc, which the appimages made here will do as long as you build them on a glibc distro. Then you just need to add the nvidia icds to VK_DRIVER_FILES to be able to use it without problem.

If you don’t have the proprietary nvidia driver, mesa already includes nouveau support for the few GPUs where this driver actually works (NVIDIA GTX 16 series or newer).

Goes without saying that sharun handles all of this already on its own.


| Back to Index | | - |


Examples and templates

Demo examples

See the ready-to-use demo scripts in useful-tools/demo/:

Real-world examples

Browse through our production AppImage repositories for more complex examples:


| Back to Index | | - |