# 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](https://wails.io/). 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](https://github.com/AppImage/AppImageKit) 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`:

```bash
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:

```bash
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:
    

```bash
#!/bin/sh

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

This file needs to be made executable:

```bash
chmod +x appdir/AppRun
```

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

```bash
mv myapp appdir/myapp
```

2. The other option is to rename your binary executable to `AppRun` and have it executed directly:
    

```bash
mv myapp appdir/AppRun
```

### Desktop entry file

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

```ini
[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](https://specifications.freedesktop.org/menu-spec/latest/category-registry.html)
    

Check out [this](https://www.baeldung.com/linux/desktop-entry-files) 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:

```bash
./$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):

```bash
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:

```bash
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:

```bash
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:

```makefile
targets: prerequisites
	command
```

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

```bash
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:

```makefile
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](https://makefiletutorial.com/).

# 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.

<div data-node-type="callout">
<div data-node-type="callout-emoji">👀</div>
<div data-node-type="callout-text">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 <a target="_blank" rel="noopener noreferrer nofollow" href="https://github.com/jcrd/brainstack" style="pointer-events: none">here</a>.</div>
</div>
