Skip to content

Creating AppImages

probonopd edited this page Jul 13, 2018 · 77 revisions

Creating AppImages

The general workflow for creating an AppImage involves the following steps:

  1. Gather suitable binaries. If the application has already been compiled, you can use existing binaries (for example, contained in .tar.gz, deb, or rpm archives). Note that the binaries must not be compiled on newer distributions than the ones you are targeting. In other words, if you are targeting Ubuntu 9.10, you should not use binaries compiled on Ubuntu 10.04. For upstream projects, it might be advantageous to compile special builds for use in AppImages, although this is not required.
  2. Gather suitable binaries of all dependencies that are not part of the base operating systems you are targeting. For example, if you are targeting Ubuntu, Fedora, and openSUSE, then you need to gather all libraries and other dependencies that your app requires to run that are not part of Ubuntu, Fedora, and openSUSE.
  3. Create a working AppDir from your binaries. A working AppDir runs your app when you execute its AppRun file.
  4. Turn your AppDir into an AppImage. This compresses the contents of your AppDir into a single, self-mounting and self-executable file.
  5. Test your AppImage on all base operating systems you are targeting. This is an important step which you should not skip. Subtle differences in distributions make this a must. While it is possible in most cases to create AppImages that run on various distributions, this does not come automatically, but requires careful hand-tuning.

While it would theoretically be possible to do all these steps by hand, AppImageKit contains tools that greatly simplify the tasks.

Now, on a more practical note, there are different ways to generate an AppImage of your application:

  1. Use Open Build Service (OBS)
  2. Convert existing binary packages (.deb, .rpm, ...)
  3. Use Travis CI
  4. Run linuxdeployqt on your Qt application
  5. Use electron-builder for Electron-based apps
  6. Create an AppDir manually

1. Use the Open Build Service

This option is recommended for open source projects because it allows you to leverage the existing Open Build Service infrastructure, security and license compliance processes. See https://github.com/probonopd/AppImageKit/wiki/Using-Open-Build-Service for how to use this.

2. Convert existing binary packages

This option might be the easiest if you already have up-to-date packages in place, ideally a ppa for trusty or earlier or a debian repository for oldstable. In this case, you can write a small .yml recipe and in many cases are done with the package to AppImage conversion. The pkg2appimage script can run .yml recipes. See examples.

3. Bundle your Travis CI builds as AppImages

This option might be the easiest if you already have continuous builds on Travis CI in place. In this case, you can write a small scriptfile and in many cases are done with the AppImage generation. See examples.

4. Run linuxdeployqt on your Qt application

This option might be the easiest if you build your application from source code using using cmake, qmake, or make, and/or if you have a Qt-based application. In the latter case, you are probably already using windeployqt and macdeployqt and now can use linuxdeployqt in the same fashion with the -appimage argument and in many cases are done with the AppImage generation See example.

5. Use electron-builder

This option might be the easiest if you have an Electron-based application. In this case, you define AppImage as a target for Linux (default in the latest version of electron-builder) and in many cases are done with the AppImage generation. See examples.

6. Manually create an AppDir

Create an AppDir manually, then turn it into an AppImage. Start out with the example below, then check the examples on bundling certain applications or type of applications as AppImages from the right-hand side "Pages" menu.

Creating an AppDir manually

In practice, you will probably never do this by hand. So this is mainly to illustrate the concept.

Create an AppDir structure that looks (as a minimum) like this:

MyApp.AppDir/
MyApp.AppDir/AppRun
MyApp.AppDir/myapp.desktop
MyApp.AppDir/myapp.png
MyApp.AppDir/usr/bin/myapp
MyApp.AppDir/usr/lib/libfoo.so.0

The AppRun file can be a script or executable. It sets up required environment variables such as $PATH and launches the payload application. You can write your own, but in most cases it is easiest (and most error-proof) to use a precompiled one from this repository.

Of course you can leave out the library if your app does not need one, or if all libraries your app needs are already contained in every base operating system you are targeting.

No hard-coded paths

Your binary, myapp, must not contain any hardcoded paths that would prevent it from being relocateable. You can check this by running strings MyApp.AppDir/usr/bin/myapp | grep /usr. Should this return something, then you need to modify your app programmatically (e.g., by using relative paths, using binreloc, or using QString QCoreApplication::applicationDirPath()).

If you prefer not to change the source code of your app and/or would not like to recompile your app, you can also patch the binary, for example using the command

sed -i -e 's#/usr#././#g' MyApp.AppDir/usr/bin/myapp

This usually works as long as the application is not doing a chdir() which would break this workaround, because then ././ would not be pointing to $APPDIR/usr any more. You can run

strace -echdir -f ./AppRun

to see whether the application is doing a chdir() (99% of GUI applications don't).

Also see https://www.gnu.org/software/gnulib/manual/html_node/Supporting-Relocation.html

It has been a pain for many users of GNU packages for a long time that packages are not relocatable. The relocatable-prog module aims to ease the process of making a GNU program relocatable.

Note: The same is true for any helper binaries and/or libraries that your app depends on. You check this and patch it with

 cd MyApp.AppDir/usr/ 
 find . -type f -exec sed -i -e 's#/usr#././#g' {} \; 
 cd -

which replaces all occurrences of /usr with ././, which simply means "here".

myapp.desktop should contain (as a minimum):

[Desktop Entry]
Name=MyApp
Exec=myapp
Icon=myapp
Type=Application
Categories=Utilities;

Be sure to pick one of the Registered Categories, and be sure that your desktop file passes validation by using desktop-file-validate your.desktop. If you are not deploying an application with a graphical user interface (GUI) but a command line tool (for the terminal), make sure to add Terminal=true.

Then, run appimagetool on the AppDir in order to turn it into an AppImage. You can get it from this repository's Releases page (it comes as an AppImage itself; yes, we eat our own dogfood).


Creating AppImages that are compatible with many systems

For an AppImage to run on most systems, the following conditions need to be met:

  1. The AppImage needs to include all libraries and other dependencies that are not part of all of the base systems that the AppImage is intended to run on.
  2. The binaries contained in the AppImage need to be compiled on a system not newer than the oldest base system that the AppImage is intended to run on.
  3. The AppImage should actually be tested on the base systems that it is intended to run on.

Binaries compiled on old enough base system

The ingredients used in your AppImage should not be built on a more recent base system than the oldest base system your AppImage is intended to run on. Some core libaries, such as glibc, tend to break compatibility with older base systems quite frequently, which means that binaries will run on newer, but not on older base systems than the one the binaries were compiled on.

If you run into errors like this

failed to initialize: /lib/tls/i686/cmov/libc.so.6: version `GLIBC_2.11' not found

then the binary is compiled on a newer system than the one you are trying to run it on. You should use a binary that has been compiled on an older system. Unfortunately, the complication is that distributions usually compile the latest versions of applications only on the latest systems, which means that you will have a hard time finding binaries of bleeding-edge softwares that run on older systems. A way around this is to compile dependencies yourself on a not too recent base system, and/or to use LibcWrapGenerator or glibc_version_header.

When producing AppImages for the Subsurface project, I have had very good results by using CentOS 6. This distribution is not too recent (current major CentOS version minus 1) while there are still the most recent Qt and modern compilers for it in the EPEL and devtools-2 (the community equvalent of the Red Hat Developer Toolset 2) repositories. When using it for compilation, I found the resulting binaries to run on a wide variety of systems, including debian oldstable (wheezy).

Be sure to check https://github.com/probonopd/AppImages, this is how I build and host my AppImages and the build systems to produce them in the cloud using travis-ci, docker, docker-hub, and bintray. Especially check the recipes for Subsurface and Scribus.

See https://github.com/probonopd/AppImageKit/wiki/Docker-Hub-Travis-CI-Workflow for a description on how to set up a workflow involving your GitHub repository, Docker Hub, and Travis CI for a fully automated continuous build workflow.

You could also consider to link some exotic libraries statically. Yes, even Debian does that: https://lintian.debian.org/tags/embedded-library.html

libstdc++.so.6

As a general rule of thumb, please use no libstdc++.so.6 newer than the one that comes with the oldest distribution that you still want to support, i.e., the oldest still-supported LTS version (at the time of this writing, Ubuntu 14.04).

Some projects require newer C++ standards to build them. To keep the glibc dependency low you can build a newer GCC version on an older distro and use it to compile the project. If you do this, however, then your compiled application will require a newer version of the libstdc++.so.6 library than available on that distro. Bundling libstdc++.so.6 however will in most cases break compatibility with distros that have a newer library version installed into their system than the bundled one. So blindly bundling the library is not reliable. While this is primarily an issue with libstdc++.so.6 in some rare cases this might also occur with libgcc_s.so.1. That's because both libraries are part of GCC. You would have to know the library version of the host system and decide whether to use a bundled library or not before the application is started. This is exactly what the patched AppRun binary from https://github.com/darealshinji/AppImageKit-checkrt/ does. It will search for usr/optional/libstdc++/libstdc++.so.6 and usr/optional/libgcc_s/libgcc_s.so.1 inside the AppImage or AppDir. If found it will compare their internal versions with the ones found on the system and prepend their paths to LD_LIBRARY_PATH if necessary.

Testing

To ensure that the AppImage runs on the intended base systems, it should be thoroughly tested on each of them. The following testing procedure is both efficient and effective: Get the previous version of Ubuntu, Fedora, and openSUSE Live CDs and test your AppImage there. Using the three largest distributions increases the chances that your AppImage will run on other distributions as well. Using the previous (current minus one) version ensures that your end users who might not have upgraded to the latest version yet can still run your AppImage. Using Live CDs has the advantage that unlike installed systems, you always have a system that is in a factory-fresh condition that can be easily reproduced. Most developers just test their software on their main working systems, which tend to be heavily customized through the installation of additional packages. By testing on Live CDs, you can be sure that end users will get the best experience possible.

I use ISOs of Live CDs, loop-mount them, chroot into them, and run the AppImage there. This way, I need approximately 700 MB per supported base system (distribution) and can easily upgrade to newer versions by just exchanging one ISO file. The following script automates this for Ubuntu-like (Casper-based) and Fedora-like (Dract-based) Live ISOs:

sudo ./AppImageAssistant.AppDir/testappimage /path/to/elementary-0.2-20110926.iso ./AppImageAssistant.AppImage

Common mistake

Please DO NOT put an AppImage into another archive like a .zip or .tar.gz. While it may be tempting to avoid users having to set permission, this breaks desktop integration with the optional appimaged daemon, among other things. Besides, the beauty of the AppImage format is that you never need to unpack anything. Furthermore, packing an AppImage into some form of archive prevents the AppImage from being added to the central catalog of available AppImages at https://github.com/AppImage/AppImageHub.

Environment variables

By default, AppRun sets some variables such as LD_LIBRARY_PATH before executing the payload application. While this is sufficient in most cases, it may lead to issues if the payload application launches other applications that reside in the base system, that is, outside of the AppImage. KDevelop is an example of such an application. In these cases, the appimage-exec-wrapper library can be used together with the AppImage distribution mechanism. Place the library somewhere in your AppImage and point LD_PRELOAD to it before launching your application. Whenever your application invokes a child process through execv() or execve(), this wrapper will intercept the call and see if the child process lies outside of the bundled appdir. If it does, the wrapper will attempt to undo any changes done to environment variables before launching the process, since you probably did not intend to launch it with e.g. the LD_LIBRARY_PATH you previously set for your application. linuxdeployqt, on the other hand, does entirely without setting LD_LIBRARY_PATH by setting the RPATH in libraries and executables relative to $ORIGIN.

Processor architectures

Can I build armhf or arm64 AppImages?

Yes, you can compile the AppImageKit tools for those architectures. The Open Build Service has support for it.

Can I build multiple-arch (say x86_64 + armhf) AppImages ?

No, you need one AppImage for each architecture. FatELF could solve this, but is not merged into the mainline kernel, so it is currently not an option.

Distributing AppImages

We recommend that you put the AppImage for Linux on your project's download page alongside the dmg for macOS and the exe for Windows, like so:

For open source projects, we recommend that you publish your AppImage in addition on GitHub Releases.

You also may want to add it to AppImageHub, the crowd-sourced directory of available AppImages.

Even under open source licenses, distributing and/or using code in source or binary form may create certain legal obligations, such as the distribution of the corresponding source code and build instructions for GPL licensed binaries, and displaying copyright statements and disclaimers. As the author of an application which you are distributing as an AppImage, you are responsible to obey all licenses for any third-party dependencies that you include in your AppImage, and ensure that their licenses and source code are made available, where required, together with the release binaries. AppImageKit itself is released under the permissive MIT license.

Troubleshooting

Applications "hang" when inside an AppImage

Payload applications must not catch more SIGCHLD than they need.

References: