Building Your First .NET MAUI AppImage: A Complete Walkthrough
Learn how to package .NET MAUI Linux applications as portable AppImages using the OpenMaui.AppImage CLI tool, complete with custom icons, metadata, and one-click installation dialogs.
Introduction to AppImage for .NET
If you've built a .NET MAUI application for Linux, you've likely faced the distribution challenge: how do you package your app so users can easily download and run it without wrestling with dependencies, package managers, or complex installation procedures?
Enter AppImage—a universal packaging format for Linux that bundles your application and its dependencies into a single executable file. Think of it as the Linux equivalent of a portable Windows .exe or macOS .app bundle. Users download the AppImage, mark it executable, and run it. No installation required.
Why AppImage for .NET MAUI?
The .NET MAUI framework brings cross-platform development to Linux, but distributing Linux apps has traditionally been fragmented across different package formats (.deb, .rpm, Flatpak, Snap). AppImage solves this by:
- Working across distributions: One AppImage runs on Ubuntu, Fedora, Arch, and more
- No root required: Users can run apps without system-level installation
- Self-contained: All dependencies travel with your app
- Portable: Download, run, and optionally install with desktop integration
The OpenMaui.AppImage tool streamlines the entire packaging process, automatically detecting your executable, extracting icons from your .csproj file, and creating a polished AppImage with an integrated installation dialog.
AppImages bring the 'download and run' simplicity of Windows executables to the Linux ecosystem, making your .NET MAUI apps truly portable across distributions.
What You'll Build
By the end of this walkthrough, you'll have:
- A portable
.AppImagefile containing your MAUI app - Automatic executable and icon detection from your project
- A zenity-based installation dialog that lets users install or run your app
- Desktop integration with application menu entries and custom icons
- Support for both self-contained and framework-dependent deployments
Installing the OpenMaui.AppImage Tool
The OpenMaui.AppImage tool is distributed as a .NET global tool, making installation straightforward on any system with the .NET SDK installed.
Prerequisites
Before installing the tool, ensure you have:
- .NET 9.0 SDK or later installed (download here)
- appimagetool installed on your Linux system
- zenity (optional, for the graphical installer dialog)
Installing appimagetool
The appimagetool utility is required to create AppImages. Install it using one of these methods:
# Download the official AppImage
wget https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage
chmod +x appimagetool-x86_64.AppImage
sudo mv appimagetool-x86_64.AppImage /usr/local/bin/appimagetool
# Or install via package manager
# Ubuntu/Debian
sudo apt install appimagetool
# Fedora
sudo dnf install appimagetool
# Arch Linux
yay -S appimagetool-bin
Installing OpenMaui.AppImage
Once you have the prerequisites, install the tool as a .NET global tool:
dotnet tool install -g OpenMaui.AppImage
This makes the openmaui-appimage command available system-wide. Verify the installation:
openmaui-appimage --help
You should see the command-line options and usage information.
Building from Source (Optional)
If you prefer to build from source or contribute to the project:
git clone https://github.com/AuroraNetworks/openmaui-appimage.git
cd openmaui-appimage
dotnet build
dotnet pack
dotnet tool install -g --add-source ./src/OpenMaui.AppImage/bin/Release OpenMaui.AppImage
The tool is now ready to package your MAUI applications!
Publishing Your MAUI App for Linux
Before creating an AppImage, you need to publish your .NET MAUI application for Linux. The OpenMaui.AppImage tool works with both self-contained and framework-dependent deployments.
Understanding Deployment Models
Self-contained deployment bundles the .NET runtime with your app, creating a larger but completely standalone package:
dotnet publish -c Release -r linux-x64 --self-contained true
Framework-dependent deployment assumes the user has .NET installed, resulting in smaller packages:
dotnet publish -c Release -r linux-x64 --self-contained false
For AppImages, self-contained is often preferred since it guarantees your app runs regardless of the system's .NET installation.
The OpenMaui.AppImage tool automatically detects your executable, extracts icons from your project file, and creates a polished installation experience—all from a single command.
Publishing Your MAUI Project
Navigate to your MAUI project directory and publish for Linux:
cd YourMauiApp
dotnet publish -c Release -r linux-x64 --self-contained true -p:PublishSingleFile=false
Important: Set PublishSingleFile=false for MAUI apps. While single-file publishing works for console apps, MAUI applications need their resources and native libraries accessible.
The published output will be in:
bin/Release/net9.0/linux-x64/publish/
Optimizing Your Icon for AppImage
The OpenMaui.AppImage tool automatically detects and extracts icons from your project's MauiIcon configuration. Here's how the tool parses your .csproj:
private string? TryGetIconFromCsproj(string inputDir, string execName)
{
var doc = XDocument.Load(csprojPath);
var mauiIcon = doc.Descendants("MauiIcon").FirstOrDefault();
var includeAttr = mauiIcon.Attribute("Include")?.Value;
var foregroundAttr = mauiIcon.Attribute("ForegroundFile")?.Value;
var bgColorAttr = mauiIcon.Attribute("BackgroundColor")?.Value;
// Composite background + foreground into final icon
if (fgPath != null && File.Exists(fgPath))
{
return CompositeIcon(bgPath, fgPath, bgColorAttr, colorAttr);
}
return bgPath;
}
Your .csproj might look like this:
<ItemGroup>
<MauiIcon Include="Resources\AppIcon\appicon.svg"
ForegroundFile="Resources\AppIcon\appiconfg.svg"
Color="#FFFFFF"
BackgroundColor="#512BD4" />
</ItemGroup>
The tool will automatically composite the foreground and background into a rounded-corner icon suitable for Linux desktop environments.
Verifying the Published Output
Before packaging, verify your publish directory contains:
- Your main executable or
.dllfile - All required native libraries (
.sofiles) - Resource files and assets
- Icon files (if not auto-detected from
.csproj)
List the contents:
ls -lh bin/Release/net9.0/linux-x64/publish/
You're now ready to create your AppImage!
Creating Your First AppImage
With your MAUI app published, creating an AppImage is a single command. The OpenMaui.AppImage tool handles all the complexity—detecting executables, extracting icons, creating the AppDir structure, and bundling everything into a portable AppImage.
Basic AppImage Creation
The minimal command requires three parameters:
openmaui-appimage \
--input bin/Release/net9.0/linux-x64/publish \
--output MyMauiApp.AppImage \
--name "My MAUI App"
The tool will:
- Auto-detect your executable by scanning for ELF binaries or
.dllfiles with matching.runtimeconfig.json - Extract icons from your
.csprojMauiIconconfiguration - Create an AppDir with the proper structure
- Generate an AppRun script that handles both self-contained and framework-dependent apps
- Create a
.desktopfile for desktop integration - Bundle everything using
appimagetool
Here's what the auto-detection looks like under the hood:
private string? AutoDetectExecutable(string inputDir, string appName)
{
// Strategy 1: Look for native ELF executable (self-contained)
var files = Directory.GetFiles(inputDir);
foreach (var file in files)
{
var firstBytes = new byte[4];
using (var fs = File.OpenRead(file))
fs.Read(firstBytes, 0, 4);
// ELF magic number: 0x7F 'E' 'L' 'F'
if (firstBytes[0] == 0x7F && firstBytes[1] == 'E' &&
firstBytes[2] == 'L' && firstBytes[3] == 'F')
{
return fileName;
}
}
// Strategy 2: Look for .dll with .runtimeconfig.json
var runtimeConfigs = Directory.GetFiles(inputDir, "*.runtimeconfig.json");
foreach (var config in runtimeConfigs)
{
var baseName = Path.GetFileNameWithoutExtension(config)
.Replace(".runtimeconfig", "");
var dllPath = Path.Combine(inputDir, baseName + ".dll");
if (File.Exists(dllPath) &&
!baseName.StartsWith("Microsoft.") &&
!baseName.StartsWith("System."))
{
return baseName;
}
}
}
Advanced Options
For more control over the AppImage creation, use additional options:
openmaui-appimage \
--input bin/Release/net9.0/linux-x64/publish \
--output MyMauiApp-1.2.3-x86_64.AppImage \
--name "My MAUI App" \
--executable MyMauiApp \
--icon Resources/AppIcon/appicon.png \
--category "Development" \
--app-version "1.2.3" \
--comment "A powerful MAUI application for Linux"
Option Reference:
--input, -i: Path to published app directory (required)--output, -o: Output AppImage file path (required)--name, -n: Application display name (required)--executable, -e: Executable name (auto-detected if omitted)--icon: Custom icon path (auto-detected from.csprojif omitted)--category, -c: Desktop category (default: "Utility")--app-version: Version string (default: "1.0.0")--comment: Application description
What Gets Created
The tool creates a complete AppDir structure:
MyMauiApp.AppDir/
├── AppRun # Launch script
├── MyMauiApp.desktop # Desktop entry
├── MyMauiApp.svg # Application icon
└── usr/
├── bin/
│ ├── MyMauiApp # Your app files
│ ├── MyMauiApp.dll
│ ├── libSkiaSharp.so
│ └── ... # All dependencies
└── share/
├── applications/
│ └── MyMauiApp.desktop
└── icons/
└── hicolor/
├── scalable/apps/
│ └── MyMauiApp.svg
└── 256x256/apps/
└── MyMauiApp.png
The AppRun script is the heart of the AppImage, handling installation, runtime detection, and app launching:
#!/bin/bash
SELF=$(readlink -f "$0")
HERE=${SELF%/*}
export APPIMAGE_NAME="My MAUI App"
export APPIMAGE_VERSION="1.2.3"
export PATH="$HERE/usr/bin:$PATH"
export LD_LIBRARY_PATH="$HERE/usr/bin:$LD_LIBRARY_PATH"
# Check if self-contained or framework-dependent
if [ -x "$HERE/usr/bin/$EXEC_NAME" ]; then
exec "$HERE/usr/bin/$EXEC_NAME" "$@"
else
exec dotnet "$EXEC_NAME.dll" "$@"
fi
Build Output
When the build completes successfully, you'll see:
Building AppImage for My MAUI App...
Input: bin/Release/net9.0/linux-x64/publish
Output: MyMauiApp.AppImage
Auto-detected executable: MyMauiApp
Auto-detected icon: Resources/AppIcon/appicon.svg
Creating AppDir at: /tmp/appimage-abc123/MyMauiApp.AppDir
Copying application files...
Creating AppRun script...
Creating .desktop file...
Setting up icon...
Creating AppImage...
AppImage created successfully: MyMauiApp.AppImage
To run:
chmod +x MyMauiApp.AppImage
./MyMauiApp.AppImage
Your AppImage is now ready to distribute!
Testing and Running the AppImage
Once your AppImage is built, testing it is straightforward. The AppImage format includes a built-in installation dialog powered by zenity, giving users a polished first-run experience.
Making the AppImage Executable
First, ensure the AppImage has execute permissions:
chmod +x MyMauiApp.AppImage
First Run: The Installation Dialog
When a user runs your AppImage for the first time, they're greeted with a zenity dialog offering installation options:
./MyMauiApp.AppImage
The dialog displays:
- Application name and version
- Custom icon (auto-detected from your project)
- Description (from the
--commentoption) - Two buttons: "Install" or "Run Only"
This is powered by the AppRun script's zenity integration:
if command -v zenity &> /dev/null; then
zenity --question --title="$APPIMAGE_NAME" \
--text="<b>$APPIMAGE_NAME</b>\nVersion $APPIMAGE_VERSION\n\n$APPIMAGE_COMMENT\n\nWould you like to install this application?" \
--ok-label="Install" --cancel-label="Run Only" \
--width=350 $ICON_OPT 2>/dev/null
ZENITY_EXIT=$?
if [ $ZENITY_EXIT -eq 0 ]; then
do_install
zenity --info --title="Installation Complete" \
--text="$APPIMAGE_NAME has been installed.\n\nYou can find it in your application menu." \
--width=300 $ICON_OPT 2>/dev/null
fi
fi
What Happens When Users Click "Install"
The installation process:
- Copies the AppImage to
~/.local/bin/ - Creates a
.desktopfile in~/.local/share/applications/ - Installs the icon to
~/.local/share/icons/hicolor/ - Updates the desktop database for menu integration
- Creates a marker file in
~/.local/share/openmaui-installed/
Here's the installation function from the AppRun script:
do_install() {
APPIMAGE_BASENAME=$(basename "$APPIMAGE")
SANITIZED=$(echo "$APPIMAGE_NAME" | tr ' ' '_')
mkdir -p "$BIN_DIR" "$APPS_DIR" "$ICONS_DIR_SCALABLE" "$ICONS_DIR_256" "$INSTALLED_MARKER"
# Copy AppImage
cp "$APPIMAGE" "$BIN_DIR/$APPIMAGE_BASENAME"
chmod +x "$BIN_DIR/$APPIMAGE_BASENAME"
# Copy icon
if [ -f "$HERE/${SANITIZED}.svg" ]; then
cp "$HERE/${SANITIZED}.svg" "$ICONS_DIR_SCALABLE/${SANITIZED}.svg"
fi
# Create .desktop file with proper WM_CLASS
WM_CLASS=$(echo "$APPIMAGE_NAME" | tr -d ' _')
cat > "$APPS_DIR/${SANITIZED}.desktop" << DESKTOP
[Desktop Entry]
Type=Application
Name=$APPIMAGE_NAME
Comment=$APPIMAGE_COMMENT
Exec=$BIN_DIR/$APPIMAGE_BASENAME
Icon=$SANITIZED
Categories=$APPIMAGE_CATEGORY;
Terminal=false
StartupWMClass=$WM_CLASS
X-AppImage-Version=$APPIMAGE_VERSION
DESKTOP
# Update desktop database
command -v update-desktop-database &> /dev/null && update-desktop-database "$APPS_DIR" 2>/dev/null
}
After installation, the app appears in the system application menu and can be launched like any native application.
Running Without Installation
If users click "Run Only", the app launches immediately without system integration. This is perfect for:
- Testing before committing to installation
- Portable use from USB drives or network shares
- Temporary access without cluttering the system
Command-Line Options
Your AppImage supports several command-line flags:
# Force show installation dialog
./MyMauiApp.AppImage --install
# Uninstall the application
./MyMauiApp.AppImage --uninstall
# Show help
./MyMauiApp.AppImage --help
The --uninstall option removes:
- The AppImage from
~/.local/bin/ - The
.desktopfile - All installed icons
- The installation marker
And updates the desktop database to remove menu entries.
Testing Desktop Integration
After installation, verify desktop integration:
# Check the .desktop file
cat ~/.local/share/applications/My_MAUI_App.desktop
# Verify icon installation
ls ~/.local/share/icons/hicolor/scalable/apps/My_MAUI_App.*
# Launch from the command line
gtk-launch My_MAUI_App.desktop
You should also see your app in:
- GNOME Activities / Application Grid
- KDE Application Launcher
- Xfce Application Finder
- Any other desktop environment's application menu
Testing on Different Distributions
The beauty of AppImage is cross-distribution compatibility. Test your AppImage on:
- Ubuntu/Debian-based: Pop!_OS, Linux Mint, elementary OS
- Fedora/RHEL-based: Fedora Workstation, CentOS Stream
- Arch-based: Manjaro, EndeavourOS
- Others: openSUSE, Solus, Void Linux
The same AppImage should work across all of them without modification!
Troubleshooting Common Issues
Even with automated tooling, you may encounter issues when creating or running AppImages. Here are solutions to the most common problems.
Issue: "Could not find executable"
Error message:
Error: Could not find executable 'MyApp' in bin/Release/net9.0/linux-x64/publish
Available DLLs: MyApp.dll, Microsoft.Maui.dll, ...
Use --executable to specify the correct name.
Cause: The tool couldn't auto-detect your main executable.
Solution 1: Explicitly specify the executable name:
openmaui-appimage \
--input bin/Release/net9.0/linux-x64/publish \
--output MyApp.AppImage \
--name "My App" \
--executable MyApp
Solution 2: If your app name contains spaces, try the version without spaces:
--executable MyMauiApp # Instead of "My Maui App"
Issue: "appimagetool not found"
Error message:
Error: appimagetool not found.
Please install appimagetool:
wget https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage
chmod +x appimagetool-x86_64.AppImage
sudo mv appimagetool-x86_64.AppImage /usr/local/bin/appimagetool
Cause: The appimagetool utility isn't installed or isn't in your PATH.
Solution: Follow the installation instructions in the error message, or install via your package manager:
# Ubuntu/Debian
sudo apt install appimagetool
# Fedora
sudo dnf install appimagetool
# Arch
yay -S appimagetool-bin
Issue: Icon Not Displaying
Symptom: The AppImage works but shows a generic icon in the application menu.
Cause: Icon wasn't found or properly embedded.
Solution 1: Explicitly provide an icon:
openmaui-appimage \
--icon Resources/AppIcon/appicon.png \
# ... other options
Solution 2: Ensure your .csproj has a MauiIcon entry:
<ItemGroup>
<MauiIcon Include="Resources\AppIcon\appicon.svg" />
</ItemGroup>
Solution 3: Use SVG format for best results:
# Convert PNG to SVG if needed
convert appicon.png -background none -gravity center -extent 256x256 appicon.svg
Issue: "dotnet: command not found" When Running AppImage
Symptom: Framework-dependent AppImage fails with "dotnet: command not found".
Cause: The user doesn't have .NET runtime installed, and your AppImage isn't self-contained.
Solution 1: Publish as self-contained:
dotnet publish -c Release -r linux-x64 --self-contained true
Solution 2: Document the .NET requirement in your app description:
openmaui-appimage \
--comment "Requires .NET 9.0 runtime. Install from https://dotnet.microsoft.com" \
# ... other options
Solution 3: The AppRun script attempts to find dotnet in common locations:
DOTNET_CMD="dotnet"
if ! command -v dotnet &> /dev/null; then
for d in /usr/share/dotnet /usr/lib/dotnet /opt/dotnet "$HOME/.dotnet"; do
[ -x "$d/dotnet" ] && DOTNET_CMD="$d/dotnet" && break
done
fi
But self-contained is the most reliable approach.
Issue: AppImage Crashes on Wayland
Symptom: App works on X11 but crashes on Wayland.
Cause: Some MAUI/GTK components may have Wayland compatibility issues.
Solution: Force X11 mode via environment variable:
GDK_BACKEND=x11 ./MyMauiApp.AppImage
Or modify the AppRun script to default to X11:
# Add before exec command
export GDK_BACKEND=x11
Issue: Permission Denied
Symptom: ./MyApp.AppImage: Permission denied
Cause: AppImage doesn't have execute permissions.
Solution:
chmod +x MyApp.AppImage
./MyApp.AppImage
Issue: "FUSE not available" Error
Error message:
Cannot mount AppImage, please check your FUSE setup.
Cause: Modern AppImages use FUSE to mount their filesystem, but FUSE isn't configured.
Solution 1: Install FUSE:
# Ubuntu/Debian
sudo apt install fuse libfuse2
# Fedora
sudo dnf install fuse fuse-libs
# Arch
sudo pacman -S fuse2
Solution 2: Extract and run manually:
./MyApp.AppImage --appimage-extract
./squashfs-root/AppRun
Issue: Missing Native Libraries
Symptom: App crashes with "cannot open shared object file" errors.
Cause: Your app depends on system libraries not bundled in the AppImage.
Solution 1: Ensure publish includes all native dependencies:
dotnet publish -c Release -r linux-x64 --self-contained true -p:PublishTrimmed=false
Solution 2: Check which libraries are missing:
ldd bin/Release/net9.0/linux-x64/publish/MyApp
Copy any missing .so files to your publish directory before creating the AppImage.
Debugging AppRun Scripts
To debug the AppRun script, extract the AppImage and run with bash tracing:
./MyApp.AppImage --appimage-extract
bash -x squashfs-root/AppRun
This shows each command as it executes, helping identify where failures occur.
Comments
No comments yet. Be the first to share your thoughts!
Leave a Comment
Enter your name and email to verify before commenting.
Enter the 6-digit code sent to your email.