Packaging apps for Linux with AppImage

Packaging apps for Linux with AppImage

AppImage is one of many popular “image”-based packaging options for modern Linux distros, along with flatpak and snap. I chose to package my software project with AppImage because the software is written in Go with Wails. It produces a single binary, which I felt would require minimal packaging effort, and AppImage seemed like the easiest option. The binary still needs specific runtime libraries, and AppImage allows us to package these in the AppImage file, making it truly standalone. The best part is, modern tooling makes this process very straightforward.

First we’ll see how it all works, then I’ll describe an easy implementation of a build script used in my project 👉

Building AppImages

The modern ecosystem of AppImage tooling provides an essential tool, appimagetool, that can be included in a build pipeline to produce an AppImage file from a basic directory structure.

Getting the tool

Downloading appimagetool for your architecture is easy. Here’s how you can do it using curl:

ARCH="x86_64"
APPIMAGETOOL="appimagetool-$ARCH.AppImage"

curl -L -o $APPIMAGETOOL https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-$ARCH.AppImage
chmod +x $APPIMAGETOOL

Setting up the app directory

Now the tool can be used to create an AppImage file from an app directory. Setting up this directory is easy due to its minimal structure:

appdir/
    AppRun
    myapp.desktop
    myapp.png

The app directory only requires three files:

  • An executable file named AppRun

    • This can be your application binary itself or a script that performs set up before executing your binary
  • A .desktop file in the Desktop Entry specification format, used by desktop environments on Linux to launch applications

  • An icon PNG file

Let’s explore the first two in more detail.

AppRun file

This file is the entry point into your package executed by the AppImage package structure. There are two approaches to providing this file:

  1. Use a Bash script to perform setup before executing your binary:
#!/bin/sh

cd "$(dirname "$0")"
# Setup here
exec ./myapp

This file needs to be made executable:

chmod +x appdir/AppRun

Next, simply move your binary into the directory so it will be executed by the script:

mv myapp appdir/myapp
  1. The other option is to rename your binary executable to AppRun and have it executed directly:
mv myapp appdir/AppRun

Desktop entry file

Desktop entry files have an INI-style format that looks like this:

[Desktop Entry]
Type=Application
Name=MyApp
Comment=The next big thing
Icon=myapp
Categories=Utility

The key-value pairings are self-explanatory. The important things to note are:

  • The Icon value must match the filename of your icon, i.e. myapp refers to myapp.png in the directory

  • appimagetool requires the Categories key to be present. See a list of valid categories here

Check out this tutorial for a more in-depth look at these files.

Building the AppImage

With the tool downloaded and the app directory structure in place, producing an AppImage file requires only one command:

./$APPIMAGETOOL appdir

If the process succeeds, you will find the AppImage file in the current directory. The final directory set up would look like this (assuming a x86_64 architecture):

myapp-project/
    appdir/
        AppRun
        myapp.desktop
        myapp.png
    appimagetool-x86_64.AppImage
    MyApp-x86_64.AppImage

Finally, make the AppImage file executable and run it:

chmod +x MyApp-x86_64.AppImage
./MyApp-x86_64.AppImage

Makefile implementation

Makefiles may seem archaic to some, but I find they’re perfectly suited for declaring simple build pipelines like the one above. Here’s a Makefile I created for the Wails project:

ARCH ?= x86_64

BIN = build/bin/brainstack
APPIMAGETOOL = build/appimagetool-$(ARCH).AppImage
APPIMAGE = Brainstack-$(ARCH).AppImage

$(BIN):
    wails build

appimage: build/$(APPIMAGE)

$(APPIMAGETOOL):
    curl -L -o $(APPIMAGETOOL) https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-$(ARCH).AppImage
    chmod +x $(APPIMAGETOOL)

build/$(APPIMAGE): $(BIN) $(APPIMAGETOOL)
    cp $(BIN) appdir/AppRun
    ./$(APPIMAGETOOL) appdir
    chmod +x $(APPIMAGE)
    mv $(APPIMAGE) build

.PHONY: appimage

Makefiles follow a simple paradigm. Rules with targets and prerequisites are declared like this:

targets: prerequisites
    command

Let’s walk through what happens when we run the appimage rule found in the above Makefile:

make appimage

The first thing to note is that the target of this rule is phony, as declared at the bottom of the Makefile. This means the target doesn’t represent a real file, so it will always run when called from the command-line like above. This is useful for high-level rules intended to be run by hand or from another script. When the appimage rule runs, it checks its prerequisites to find that build/$(APPIMAGE) doesn’t exist, so it in turn runs the matching rule:

build/$(APPIMAGE): $(BIN) $(APPIMAGETOOL)
    cp $(BIN) appdir/AppRun
    ./$(APPIMAGETOOL) appdir
    chmod +x $(APPIMAGE)
    mv $(APPIMAGE) build

Because this rule depends on the binary built by Wails and the downloaded tool, those rules will be triggered to produce the requisite files. Lastly, the rule’s commands are executed, which copy the application binary into the app directory, run the appimagetool to produce an AppImage file, make the file executable, and move the file into the build directory to keep the project directory clean. It’s that easy!

You can learn more about Makefiles here.

Conclusion

Packaging software as AppImages is one of the easiest options available to us on modern Linux desktops. The simple build pipeline can be integrated into CI systems such as GitHub Actions to build and publish standalone AppImage files automatically when you release your software!

Check back for more articles on this topic as I explore it further.

👀
By the way, you may have noticed my Wails project is called Brainstack. It’s a minimalist desktop app for intuitive task management, a.k.a. everyone’s favorite to-do list app! It’s still a work-in-progress at the time of writing, but feel free to check it out here.