Mohammed Chami
.NET Developer | Content Creator
Mohammed Chami
.NET Developer | Content Creator

This comprehensive tutorial will guide you through creating an AppImage for any Avalonia (or .NET) application. AppImage is a universal Linux package format that works across all distributions without installation.
An AppImage is essentially a compressed filesystem that contains:
YourApp.AppDir/
├── AppRun # Startup script
├── YourApp.desktop # Desktop entry file
├── yourapp.png # Application icon
├── .DirIcon # Symlink to icon
└── usr/
├── bin/
│ └── YourApp # Your executable
├── lib/
│ └── *.so # Shared libraries
└── share/
├── applications/
│ └── YourApp.desktop
└── icons/
└── hicolor/
└── 256x256/
└── apps/
└── yourapp.png
Add these properties to your YourApp.csproj:
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<!-- Version Information -->
<Version>1.0.0</Version>
<Company>YourCompany</Company>
<Product>YourApp</Product>
<Description>Your app description</Description>
<!-- Native AOT Configuration (Optional) -->
<PublishAot>true</PublishAot>
<InvariantGlobalization>false</InvariantGlobalization>
<StripSymbols>true</StripSymbols>
</PropertyGroup>
Make sure your assets are configured to be copied to output:
<ItemGroup>
<AvaloniaResource Include="Assets/**" Exclude="Assets/yoursound.mp3"/>
<Content Include="Assets/yoursound.mp3">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
</ItemGroup>
mkdir -p YourApp.AppDir/usr/bin
mkdir -p YourApp.AppDir/usr/lib
mkdir -p YourApp.AppDir/usr/share/applications
mkdir -p YourApp.AppDir/usr/share/icons/hicolor/256x256/apps
Create YourApp.AppDir/YourApp.desktop:
[Desktop Entry]
Name=YourApp
Exec=YourApp
Icon=yourapp
Type=Application
Categories=Utility;Office;
Comment=Your app description
Terminal=false
StartupWMClass=YourApp
Important Fields:
Name: Display name of your applicationExec: Must match your executable nameCategories: See freedesktop.org categoriesStartupWMClass: Should match your app’s window classCreate YourApp.AppDir/AppRun:
#!/bin/bash
# AppRun script for YourApp
SELF=$(readlink -f "$0")
HERE=${SELF%/*}
# Export library paths
export LD_LIBRARY_PATH="${HERE}/usr/lib:${HERE}/usr/bin:${LD_LIBRARY_PATH}"
export PATH="${HERE}/usr/bin:${PATH}"
# Set data directory to user's home to avoid read-only filesystem issues
export XDG_DATA_HOME="${XDG_DATA_HOME:-$HOME/.local/share}"
export XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}"
# Change to user's home directory (not the AppImage mount point)
cd "$HOME"
# Run the application
exec "${HERE}/usr/bin/YourApp" "$@"
Make it executable:
chmod +x YourApp.AppDir/AppRun
Key Points:
LD_LIBRARY_PATH: Ensures shared libraries are foundXDG_DATA_HOME & XDG_CONFIG_HOME: Ensures app writes to user’s home, not the read-only AppImage mountcd "$HOME": Prevents issues with read-only filesystemexec: Replaces the shell process with your appCopy your icon:
cp YourApp/Assets/youricon.png YourApp.AppDir/yourapp.png
cp YourApp/Assets/youricon.png YourApp.AppDir/usr/share/icons/hicolor/256x256/apps/yourapp.png
Create a symlink for .DirIcon:
cd YourApp.AppDir
ln -s yourapp.png .DirIcon
cd ..
dotnet publish YourApp/YourApp.csproj \
-c Release \
-r linux-x64 \
--self-contained \
-o ./publish/linux-x64
dotnet publish YourApp/YourApp.csproj \
-c Release \
-r linux-x64 \
--self-contained \
-p:PublishAot=true \
-p:PublishSingleFile=true \
-p:PublishTrimmed=true \
-o ./publish/linux-x64
Publish Options Explained:
-c Release: Release configuration (optimized)-r linux-x64: Target Linux 64-bit--self-contained: Include .NET runtime-p:PublishAot=true: Enable Native AOT compilation-p:PublishSingleFile=true: Create single executable-p:PublishTrimmed=true: Remove unused codecp ./publish/linux-x64/YourApp YourApp.AppDir/usr/bin/
Avalonia apps typically need these libraries:
# Copy all .so files
for lib in ./publish/linux-x64/*.so; do
if [ -f "$lib" ]; then
cp "$lib" YourApp.AppDir/usr/lib/
fi
done
Common libraries:
libSkiaSharp.so – Graphics renderinglibHarfBuzzSharp.so – Text shapinglibAvaloniaNative.so – Native platform support# Copy any additional files (sounds, data files, etc.)
cp ./publish/linux-x64/yoursound.mp3 YourApp.AppDir/usr/bin/
cp YourApp.AppDir/YourApp.desktop YourApp.AppDir/usr/share/applications/
wget https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage
chmod +x appimagetool-x86_64.AppImage
ARCH=x86_64 ./appimagetool-x86_64.AppImage YourApp.AppDir YourApp-1.0.0-x86_64.AppImage
Or if appimagetool is installed system-wide:
ARCH=x86_64 appimagetool YourApp.AppDir YourApp-1.0.0-x86_64.AppImage
Create build-appimage.sh:
#!/bin/bash
set -e # Exit on error
VERSION="1.0.0"
ARCH="x86_64"
APP_NAME="YourApp"
APPDIR="${APP_NAME}.AppDir"
echo "Building ${APP_NAME} AppImage v${VERSION}..."
# Check for appimagetool
if ! command -v appimagetool &> /dev/null; then
echo " appimagetool not found. Downloading..."
wget -q https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-${ARCH}.AppImage -O appimagetool
chmod +x appimagetool
APPIMAGETOOL="./appimagetool"
else
APPIMAGETOOL="appimagetool"
fi
# Clean previous builds
echo " Cleaning previous builds..."
rm -rf ./publish/linux-x64
rm -rf ./${APPDIR}/usr
# Publish the application
echo " Publishing application..."
dotnet publish ${APP_NAME}/${APP_NAME}.csproj \
-c Release \
-r linux-x64 \
--self-contained \
-o ./publish/linux-x64
# Create AppDir structure
echo " Creating AppImage directory structure..."
mkdir -p ${APPDIR}/usr/bin
mkdir -p ${APPDIR}/usr/lib
mkdir -p ${APPDIR}/usr/share/applications
mkdir -p ${APPDIR}/usr/share/icons/hicolor/256x256/apps
# Copy application files
echo " Copying application files..."
cp ./publish/linux-x64/${APP_NAME} ${APPDIR}/usr/bin/
# Copy shared libraries
for lib in ./publish/linux-x64/*.so; do
if [ -f "$lib" ]; then
cp "$lib" ${APPDIR}/usr/lib/
echo " Copied $(basename $lib)"
fi
done
# Copy assets (customize based on your needs)
if [ -f "./publish/linux-x64/yourasset.mp3" ]; then
cp ./publish/linux-x64/yourasset.mp3 ${APPDIR}/usr/bin/
fi
# Copy icon
if [ -f "${APP_NAME}/Assets/youricon.png" ]; then
cp ${APP_NAME}/Assets/youricon.png ${APPDIR}/yourapp.png
cp ${APP_NAME}/Assets/youricon.png ${APPDIR}/usr/share/icons/hicolor/256x256/apps/yourapp.png
fi
# Copy desktop file
cp ${APPDIR}/${APP_NAME}.desktop ${APPDIR}/usr/share/applications/
# Make AppRun executable
chmod +x ${APPDIR}/AppRun
# Build AppImage
echo "Building AppImage..."
ARCH=${ARCH} ${APPIMAGETOOL} ${APPDIR} ${APP_NAME}-${VERSION}-${ARCH}.AppImage
echo " AppImage created: ${APP_NAME}-${VERSION}-${ARCH}.AppImage"
Make it executable and run:
chmod +x build-appimage.sh
./build-appimage.sh
Symptom: AppImage runs but app doesn’t appear
Solutions:
LD_LIBRARY_PATH includes both usr/lib and usr/bin.so files are copied to usr/lib# Debug by running AppRun directly
./YourApp.AppDir/AppRun
Symptom: Error about missing .so files
Solutions:
.so filesAppDir/usr/liblibSkiaSharp.solibHarfBuzzSharp.solibAvaloniaNative.so# List all .so files in publish directory
find ./publish/linux-x64 -name "*.so"
Symptom: AppImage shows default icon
Solutions:
usr/share/icons/hicolor/256x256/apps/ln -s yourapp.png .DirIconSymptom: App settings/data lost after restart
Solutions:
XDG_DATA_HOME and XDG_CONFIG_HOME~/.local/share/YourApp not to AppImage mountcd "$HOME" in AppRun before launching appSymptom: Sounds, images, or other assets missing
Solutions:
usr/bin directoryCopyToPublishDirectory set<Content Include="Assets/sound.mp3">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
Symptom: AppImage file is very large (>100MB)
Solutions:
-p:PublishAot=true-p:PublishTrimmed=true-p:PublishSingleFile=true-p:StripSymbols=trueTypical sizes:
# Make executable
chmod +x YourApp-1.0.0-x86_64.AppImage
# Run it
./YourApp-1.0.0-x86_64.AppImage
# Test on different distributions (recommended)
# - Ubuntu/Debian
# - Fedora/RHEL
# - Arch Linux
# - openSUSE
git tag v1.0.0
git push origin v1.0.0
YourApp-1.0.0-x86_64.AppImage # Download
wget https://github.com/yourusername/yourapp/releases/download/v1.0.0/YourApp-1.0.0-x86_64.AppImage
# Make executable
chmod +x YourApp-1.0.0-x86_64.AppImage
# Run
./YourApp-1.0.0-x86_64.AppImage
Submit your AppImage to AppImageHub for wider distribution.
Sign your AppImage for added security:
# Generate key
gpg --gen-key
# Sign AppImage
gpg --armor --detach-sign YourApp-1.0.0-x86_64.AppImage
# Verify
gpg --verify YourApp-1.0.0-x86_64.AppImage.asc YourApp-1.0.0-x86_64.AppImage
AppImages can integrate with the desktop environment:
# Install AppImageLauncher (recommended for users)
# It automatically integrates AppImages into the system
Implement AppImage updates using AppImageUpdate:
Add to your .desktop file:
X-AppImage-Update-Information=gh-releases-zsync|username|repository|latest|YourApp-*-x86_64.AppImage.zsync
Here’s the complete structure used for ProductivityCake:
ProductivityCake/
├── ProductivityCake/ # Source code
│ ├── ProductivityCake.csproj
│ ├── Assets/
│ │ ├── icons8-cake-96.png
│ │ └── alarm.mp3
│ └── ...
├── ProductivityCake.AppDir/ # AppImage structure
│ ├── AppRun # Startup script
│ ├── ProductivityCake.desktop # Desktop entry
│ ├── productivitycake.png # Icon
│ ├── .DirIcon -> productivitycake.png
│ └── usr/
│ ├── bin/
│ │ ├── ProductivityCake # Executable
│ │ └── alarm.mp3 # Asset
│ ├── lib/
│ │ ├── libSkiaSharp.so
│ │ └── libHarfBuzzSharp.so
│ └── share/
│ ├── applications/
│ │ └── ProductivityCake.desktop
│ └── icons/
│ └── hicolor/
│ └── 256x256/
│ └── apps/
│ └── productivitycake.png
└── build-appimage.sh # Build script
Before building your AppImage, ensure:
.so files are copied to usr/libCreating an AppImage for your Avalonia application provides:
This tutorial can be adapted for any .NET application, not just Avalonia. The key is understanding the AppDir structure and ensuring all dependencies are included.
Happy packaging!