Nucleus
Packaging & distribution

Building for Linux

Configure a Nucleus app to build DEB, RPM, AppImage, and pacman packages and publish to the Snap and Flatpak stores.

In this tutorial, you'll configure a Nucleus app to produce every Linux package format — DEB, RPM, AppImage, and pacman for direct distribution, plus Snap and Flatpak for the stores — from a single linux { } block, then build and inspect the output.

Before you start

  • The Nucleus Gradle plugin is applied to your project. See Project setup.
  • You are on a Linux build host. DEB, RPM, AppImage, pacman, Snap, and Flatpak are Linux-only formats and do not cross-build from macOS or Windows.
  • Format-specific tooling is installed as noted in each step (for example libfuse2 for AppImage, snapcraft for Snap, flatpak-builder for Flatpak).

Configure the target formats

In build.gradle.kts, list the Linux formats you want in targetFormats(...) and describe the package in the linux { } block:

build.gradle.kts
nucleus {
    application {
        nativeDistributions {
            targetFormats(
                TargetFormat.Deb,
                TargetFormat.Rpm,
                TargetFormat.AppImage,
                TargetFormat.Pacman,
                TargetFormat.Snap,
                TargetFormat.Flatpak,
            )
            homepage = "https://myapp.example.com"

            linux {
                iconFile.set(project.file("icons/app.png"))
                packageName = "myapp"
                shortcut = true
                appCategory = "Utility"
                menuGroup = "Development"

                debMaintainer = "Your Name <dev@example.com>"
                debDepends = listOf("libfuse2", "libgtk-3-0", "libasound2")

                rpmLicenseType = "MIT"
                rpmRequires = listOf("gtk3", "libX11", "alsa-lib")

                pacmanDepends = listOf("gtk3", "libx11", "alsa-lib")
            }
        }
    }
}

homepage is required for DEB and RPM builds. The packaging task fails without it.

Build DEB and RPM packages

Each format has its own Gradle task named after the TargetFormat constant:

./gradlew packageDeb
./gradlew packageRpm

The plugin writes a .desktop entry into each package with the correct Categories=, MimeType=, StartupWMClass=, and Exec= lines. If your app's window class doesn't match what the window manager reports, set startupWMClass explicitly:

build.gradle.kts
linux {
    startupWMClass = "com-example-MyApp"
}

When startupWMClass is unset, Nucleus derives it from mainClass by replacing dots with hyphens.

Build an AppImage

An AppImage is a single portable executable that runs on most distributions without installation:

./gradlew packageAppImage

The build host needs libfuse2:

sudo apt-get install -y libfuse2

Configure the desktop metadata in the appImage { } sub-block:

build.gradle.kts
linux {
    appImage {
        category = AppImageCategory.Utility
        genericName = "My Application"
        synopsis = "A short description"
        desktopEntries = mapOf("Keywords" to "editor;text;")
    }
}

Output size and build time depend on nativeDistributions.compressionLevel:

CompressionLevelOutputBuild time
StoreLargestFastest
Normal (default)BalancedModerate
MaximumSmallestSlowest
build.gradle.kts
nativeDistributions {
    compressionLevel = CompressionLevel.Normal
}

Package for Arch Linux

TargetFormat.Pacman produces a pacman archive for Arch Linux and derivatives:

./gradlew packagePacman

Set Arch-specific dependencies and, if needed, an independent version string:

build.gradle.kts
linux {
    pacmanDepends = listOf("gtk3", "libx11", "alsa-lib")
    pacmanPackageVersion = "2.0.0"
}

Publish to the Snap Store

Snap targets the Snap Store. The default plugs cover desktop integration, X11 and Wayland, networking, audio playback, and OpenGL. Override confinement when your app needs filesystem access beyond home:

build.gradle.kts
linux {
    snap {
        confinement = SnapConfinement.Strict
        grade = SnapGrade.Stable
        base = "core22"
        plugs = listOf(
            SnapPlug.Desktop,
            SnapPlug.Home,
            SnapPlug.Wayland,
            SnapPlug.Network,
            SnapPlug.AudioPlayback,
        )
        compression = SnapCompression.Xz
    }
}
./gradlew packageSnap

The build host needs snapd and snapcraft --classic.

Publish to Flathub with Flatpak

Flatpak is sandboxed. Declare the permissions your app needs through finishArgs. The defaults are --share=ipc, --socket=x11, --socket=wayland, --socket=pulseaudio, and --device=dri; setting finishArgs replaces them, so include everything you need:

build.gradle.kts
linux {
    flatpak {
        runtime = "org.freedesktop.Platform"
        runtimeVersion = "23.08"
        sdk = "org.freedesktop.Sdk"

        finishArgs = listOf(
            "--share=ipc",
            "--socket=wayland",
            "--socket=pulseaudio",
            "--device=dri",
            "--filesystem=home",
            "--share=network",
        )
    }
}
./gradlew packageFlatpak

Flatpak is a store format and runs through the sandboxed pipeline: native libraries are pre-extracted and JVM arguments are redirected. If flatpak-builder or the runtime isn't installed, the task is skipped rather than failed.

Top-level protocol(...) and fileAssociation(...) entries are merged into the generated .desktop file as MimeType= values:

build.gradle.kts
nativeDistributions {
    protocol(name = "MyApp URL", schemes = listOf("myapp"))
    fileAssociation(
        mimeType = "application/x-myapp",
        extension = "myapp",
        description = "MyApp Document",
    )
}

This produces:

MimeType=x-scheme-handler/myapp;application/x-myapp;

No manual desktopEntries override is needed.

Configuration reference

linux { } exposes iconFile, packageVersion, shortcut, startupWMClass, packageName, appRelease, appCategory, menuGroup, debMaintainer, debPackageVersion, debDepends, rpmLicenseType, rpmPackageVersion, rpmRequires, pacmanPackageVersion, and pacmanDepends. Sub-blocks are appImage { }, snap { }, flatpak { }, and signing { }.

appRelease sets the package release number — the digit after the - in DEB and RPM versions. Bump it when you re-release without changing packageVersion. See the full Gradle DSL reference.

Snap and Flatpak layer their own desktop files into the user environment and ignore the traditional .desktop install paths used by DEB and RPM.

What's next