Complete Guide: Publishing and Installing Avalonia Apps on Linux

Publishing and deploying Avalonia UI applications on Linux can be tricky, especially when dealing with file sizes, dependencies, and making your app feel like a native Linux application. This comprehensive guide covers everything you need to know about publishing, optimizing, and installing Avalonia apps on Linux systems.

Table of Contents

  1. Publishing Options
  2. Optimizing App Size
  3. Handling Trimming Issues
  4. System Installation
  5. Desktop Integration
  6. Complete Installation Script
  7. Troubleshooting

Publishing Options

Self-Contained Deployment (Includes .NET Runtime)

This option bundles the .NET runtime with your app, so users don’t need .NET installed:

dotnet publish -r linux-x64 --self-contained -c Release

Pros:

  • Works on any Linux system
  • No .NET installation required
  • Predictable runtime environment

Cons:

  • Large file size (~95MB+ for simple apps)
  • Higher memory usage (~145MB+ RAM)

Framework-Dependent Deployment (Requires .NET)

This creates a smaller package but requires users to have .NET installed:

dotnet publish -r linux-x64 -c Release

Pros:

  • Much smaller size (~5-15MB)
  • Lower memory usage
  • Faster startup

Cons:

  • Users must have .NET runtime installed
  • Version compatibility issues possible

Optimizing App Size

Single File Deployment

Combines everything into one executable:

dotnet publish -r linux-x64 --self-contained -c Release -p:PublishSingleFile=true

Enable Trimming

Removes unused code to reduce size:

dotnet publish -r linux-x64 --self-contained -c Release -p:PublishSingleFile=true -p:PublishTrimmed=true

Project Configuration

Add these properties to your .csproj file for optimal size:

<PropertyGroup>
  <PublishTrimmed>true</PublishTrimmed>
  <TrimMode>partial</TrimMode>
  <InvariantGlobalization>true</InvariantGlobalization>
  <DebugType>none</DebugType>
  <DebugSymbols>false</DebugSymbols>
</PropertyGroup>

Handling Trimming Issues

Common Problem: JSON Serialization Breaks

If your app uses JSON serialization (saving data to files), trimming might remove necessary code.

Solution 1: Preserve JSON Serialization

Add to your .csproj:

<PropertyGroup>
  <PublishTrimmed>true</PublishTrimmed>
  <TrimMode>partial</TrimMode>
  <InvariantGlobalization>true</InvariantGlobalization>
  <JsonSerializerIsReflectionEnabledByDefault>true</JsonSerializerIsReflectionEnabledByDefault>
</PropertyGroup>

<ItemGroup>
  <TrimmerRootAssembly Include="System.Text.Json" />
</ItemGroup>

Solution 2: Use Source Generators (Recommended)

Create a JSON context for your models:

using System.Text.Json.Serialization;

[JsonSerializable(typeof(List<TodoTask>))]
[JsonSerializable(typeof(TodoTask))]
public partial class AppJsonContext : JsonSerializerContext
{
}

// Use it in your serialization code:
var json = JsonSerializer.Serialize(tasks, AppJsonContext.Default.ListTodoTask);
var tasks = JsonSerializer.Deserialize(json, AppJsonContext.Default.ListTodoTask);

Solution 3: Preserve Your Classes

Create a TrimmerRoots.xml file:

<linker>
  <assembly fullname="YourAppName">
    <type fullname="YourAppName.Models.TodoTask" preserve="all" />
  </assembly>
</linker>

Add to .csproj:

<ItemGroup>
  <TrimmerRootDescriptor Include="TrimmerRoots.xml" />
</ItemGroup>

File Path Issues in Single File Apps

Problem: Assembly.GetExecutingAssembly().Location returns empty string in single-file apps.

Solution: Use proper file paths:

// ❌ Don't use this:
var path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);

// ✅ Use this instead:
var homeDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
var appDataDirectory = Path.Combine(homeDirectory, ".yourapp");
Directory.CreateDirectory(appDataDirectory);
var dataFilePath = Path.Combine(appDataDirectory, "data.json");

System Installation

Understanding the Output

After publishing, even with PublishSingleFile=true, you’ll still have multiple files:

  • Your main executable
  • Native libraries (libSkiaSharp.so, libHarfBuzzSharp.so)
  • Debug symbols (.pdb files – optional)

Important: All these files must stay together – your app won’t work if you move just the executable.

Installation Methods

Method 1: Manual Installation

# Navigate to publish directory
cd ./bin/Release/net9.0/linux-x64/publish/

# Create installation directory
sudo mkdir -p /opt/YourAppName

# Copy all files
sudo cp -r ./* /opt/YourAppName/

# Make executable
sudo chmod +x /opt/YourAppName/YourAppName

# Create symlink for command line access
sudo ln -sf /opt/YourAppName/YourAppName /usr/local/bin/YourAppName

Method 2: User-Only Installation

# Create local installation directory
mkdir -p ~/.local/opt/YourAppName
mkdir -p ~/.local/bin

# Copy files
cp -r ./bin/Release/net9.0/linux-x64/publish/* ~/.local/opt/YourAppName/

# Make executable
chmod +x ~/.local/opt/YourAppName/YourAppName

# Create symlink
ln -sf ~/.local/opt/YourAppName/YourAppName ~/.local/bin/YourAppName

# Ensure ~/.local/bin is in PATH
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc

Desktop Integration

To make your app appear in the applications menu, create a desktop entry:

System-Wide Desktop Entry

sudo tee /usr/share/applications/YourAppName.desktop > /dev/null << EOF
[Desktop Entry]
Name=Your App Display Name
Comment=Brief description of your app
Exec=/opt/YourAppName/YourAppName
Icon=utilities-text-editor
Type=Application
Categories=Utility;Office;
Keywords=keyword1;keyword2;
StartupNotify=true
EOF

# Update desktop database
sudo update-desktop-database /usr/share/applications/

User-Only Desktop Entry

mkdir -p ~/.local/share/applications/

cat > ~/.local/share/applications/YourAppName.desktop << EOF
[Desktop Entry]
Name=Your App Display Name
Comment=Brief description of your app
Exec=/home/$USER/.local/opt/YourAppName/YourAppName
Icon=utilities-text-editor
Type=Application
Categories=Utility;Office;
Keywords=keyword1;keyword2;
StartupNotify=true
EOF

update-desktop-database ~/.local/share/applications/

Desktop Entry Fields

  • Name: Display name in the menu
  • Comment: Tooltip description
  • Exec: Full path to executable
  • Icon: Icon name or full path to icon file
  • Categories: Menu categories (Utility, Office, Development, Graphics, etc.)
  • Keywords: Search terms for finding your app
  • StartupNotify: Shows loading cursor when launching

Common Categories

  • Utility – System utilities and tools
  • Office – Productivity applications
  • Development – Programming and development tools
  • Graphics – Image editing and design applications
  • AudioVideo – Media players and editors
  • Game – Games and entertainment
  • Network – Network and internet applications

Complete Installation Script

Here’s a comprehensive installation script that handles everything:

#!/bin/bash

# Configuration
APP_NAME="YourAppName"
DISPLAY_NAME="Your App Display Name"
DESCRIPTION="Brief description of your app"
INSTALL_DIR="/opt/$APP_NAME"
BIN_LINK="/usr/local/bin/$APP_NAME"
DESKTOP_FILE="/usr/share/applications/$APP_NAME.desktop"

echo "Building $DISPLAY_NAME..."
dotnet publish -r linux-x64 --self-contained -c Release -p:PublishSingleFile=true -p:PublishTrimmed=true

# Check if build was successful
if [ ! -f "./bin/Release/net9.0/linux-x64/publish/$APP_NAME" ]; then
    echo "Error: Build failed or executable not found"
    exit 1
fi

echo "Installing $DISPLAY_NAME..."

# Create installation directory
sudo mkdir -p "$INSTALL_DIR"

# Copy all files from publish directory
sudo cp -r ./bin/Release/net9.0/linux-x64/publish/* "$INSTALL_DIR/"

# Make main executable
sudo chmod +x "$INSTALL_DIR/$APP_NAME"

# Create symlink for command line access
sudo ln -sf "$INSTALL_DIR/$APP_NAME" "$BIN_LINK"

# Create desktop entry for GUI integration
sudo tee "$DESKTOP_FILE" > /dev/null << EOF
[Desktop Entry]
Name=$DISPLAY_NAME
Comment=$DESCRIPTION
Exec=$INSTALL_DIR/$APP_NAME
Icon=utilities-text-editor
Type=Application
Categories=Utility;Office;
Keywords=todo;task;productivity;
StartupNotify=true
EOF

# Update desktop database
sudo update-desktop-database /usr/share/applications/ 2>/dev/null

echo ""
echo "✅ $DISPLAY_NAME installed successfully!"
echo ""
echo "You can now:"
echo "  • Run from terminal: $APP_NAME"
echo "  • Find it in your applications menu"
echo "  • Installed to: $INSTALL_DIR"
echo ""

Using the Installation Script

  1. Save the script as install.sh in your project root
  2. Update the configuration variables at the top
  3. Make it executable and run:
chmod +x install.sh
./install.sh

Troubleshooting

App Size Still Too Large?

  • Try framework-dependent deployment if users can install .NET
  • Remove debug symbols by setting <DebugSymbols>false</DebugSymbols>
  • Consider alternative UI frameworks for smaller apps

JSON Serialization Not Working After Trimming?

  1. Test without trimming first: remove -p:PublishTrimmed=true
  2. If it works without trimming, add JSON preservation settings
  3. Consider using source generators for optimal results

App Won’t Start After Moving Executable?

  • Avalonia apps need their native libraries (*.so files)
  • Always install/move the entire publish folder contents together
  • Never separate the executable from its dependencies

Desktop Entry Not Appearing?

  • Check file permissions: ls -la /usr/share/applications/YourApp.desktop
  • Update desktop database: sudo update-desktop-database /usr/share/applications/
  • Log out and back in, or restart desktop environment
  • Verify categories are correct for your desktop environment

Command Not Found After Installation?

  • Check if /usr/local/bin is in PATH: echo $PATH
  • Verify symlink exists: ls -la /usr/local/bin/YourAppName
  • Try absolute path: /usr/local/bin/YourAppName

High Memory Usage?

This is normal for .NET applications with UI frameworks. To reduce:

  • Use framework-dependent deployment
  • Consider native alternatives for resource-critical applications
  • Monitor actual memory usage vs. virtual memory allocation

Best Practices

  1. Always test trimmed builds thoroughly – functionality can break silently
  2. Use appropriate file paths – avoid Assembly.Location in single-file apps
  3. Keep native dependencies together – don’t separate .so files from executable
  4. Choose deployment type based on target audience:
    • Self-contained for general users
    • Framework-dependent for developer/technical users
  5. Include proper desktop integration – makes your app feel native
  6. Test installation script on clean system before sharing

Conclusion

While Avalonia apps are larger than traditional native applications, proper deployment strategies can create a professional, user-friendly installation experience. The trade-off between convenience and resource usage is often acceptable for most desktop applications.

Remember to test your deployment thoroughly, especially when using trimming optimizations, as they can break functionality in subtle ways that only appear in production builds.

This guide covers .NET 9.0 and Avalonia UI on Linux. Commands and paths may vary slightly between different Linux distributions.

Mohammed Chami
Mohammed Chami
Articles: 44

Leave a Reply

Your email address will not be published. Required fields are marked *