aptpod Tech Blog

株式会社アプトポッドのテクノロジーブログです

AppImageでLinuxアプリケーションを配布しよう

intdashグループの大久保です。aptpod Advent Calendar 2025の12月3日の記事を担当します。

Linuxでアプリケーションを開発するとき、その依存ライブラリ含めどう配布するかは悩ましい問題です。debのようなパッケージであれば、対応するディストリビューションごとにビルドしなければなりません。muslであれば単体で動作する実行バイナリを作れますが、依存するライブラリが多ければビルドが難しかったり、対応不可であったりします。

ここでは、Linuxのアプリケーション配布をするときに有用と思われるAppImageを紹介します。

AppImageとは

AppImageは、1つの実行ファイルに依存するファイルを内包するようにし、ディストリビューションに関わらず単体で動作するようにしたパッケージフォーマットです。

https://appimage.org/

今回は、debパッケージからAppImageを作成できる pkg2appimageを使用します。

テスト用アプリケーションの作成

開発言語は特に制約はありませんが、今回はAppImageパッケージを生成する途中でdebパッケージを経由する方法をとるので、debパッケージを簡単に作れるRustを今回は例に使います。他の方法でdebパッケージを作成してもらっても問題ありません。

ここではGTKを使用して画像を表示するシンプルなGUIアプリケーションを作成します。

Cargo.tomlは以下のように準備します。

[package]
name = "appimage-test"
authors = ["Test <test@example.com>"]
version = "0.1.0"
edition = "2024"
license = "MIT"

[dependencies]
gtk4 = "0.10"

# cargo-deb の設定
[package.metadata.deb]
maintainer = "Test <test@example.com>"
depends = "$auto"
section = "misc"
priority = "optional"
assets = [
    # コンパイルされたバイナリを /usr/bin に配置
    ["target/release/appimage-test", "usr/bin/", "755"],
    # desktopファイルを /usr/share/applications に配置
    ["appimage-test.desktop", "usr/share/applications/", "644"],
    # アイコンファイル (logo.png) を /usr/share/pixmaps/appimage-test.png として配置
    ["logo.png", "usr/share/pixmaps/appimage-test.png", "644"],
]

main.rsは以下になります。注意点としては、画像ファイルにアクセスするとき /usr/share/pixmaps/appimage-test.png と絶対パスを指定するのではなく、実行ファイルのある場所 usr/bin からの相対パスで指定していることです。AppImageは実行時に一時的なパスにファイルを配置するため、絶対パスが機能しなくなります。

use gtk4::{Application, ApplicationWindow, Image, gdk_pixbuf::Pixbuf, prelude::*};
use std::env;

fn main() {
    let application = Application::new(Some("com.example.appimage-test"), Default::default());

    application.connect_activate(build_ui);
    application.run();
}

fn build_ui(app: &Application) {
    let exe_path = env::current_exe().expect("Failed to get executable path");

    // 実行ファイルからの相対パスから usr/share/pixmaps/appimage-test.png を取得
    let exe_dir = exe_path
        .parent()
        .expect("Failed to get executable directory");
    let image_path = exe_dir.join("../share/pixmaps/appimage-test.png");

    let pixbuf = Pixbuf::from_file(&image_path).unwrap_or_else(|e| {
        panic!(
            "Failed to load image file from path {:?}. Error: {}",
            image_path, e
        );
    });

    let image = Image::from_pixbuf(Some(&pixbuf));

    let window = ApplicationWindow::new(app);
    window.set_title(Some("AppImage Test (GTK4)"));
    window.set_default_size(pixbuf.width(), pixbuf.height());

    window.set_child(Some(&image));

    window.present();
}

Linuxのdesktopファイルを適当に用意し、appimage-test.desktopと名前をつけて保存しておきます。

[Desktop Entry]
Version=1.0
Type=Application
Name=AppImage Test
GenericName=Test Application
Comment=A simple GTK4 test application
Exec=appimage-test
Icon=appimage-test
Terminal=false
Categories=Utility;Development;
StartupNotify=true

他に適当な画像を用意して logo.png として保存しておきます。

以上のものを用意すれば、cargo-deb でdebパッケージを生成しましょう。target/debian以下にdebファイルが生成されているはずです。

$ cargo deb

debパッケージからAppImageへの変換

debパッケージが用意できたら、以下のようなレシピファイルをyamlで作成します。レシピ内にUbuntuのバージョンが含まれますが、UbuntuのバージョンはdebパッケージのビルドとAppImageへの変換の実行するホスト環境、そしてレシピ内で統一しておいたほうが無難です。

app: appimage-test

ingredients:
  dist: noble
  sources:
    - deb http://archive.ubuntu.com/ubuntu/ noble main universe
  debs:
    - /path/to/appimage-test_0.1.0-1_amd64.deb

レシピファイルを作成したら変換用スクリプトをダウンロードしてきましょう。

Releases · AppImageCommunity/pkg2appimage · GitHub

ここからダウンロードした実行バイナリで変換に失敗した場合は、GitHubにある生のスクリプトを実行してやるとうまく行く場合があるようです。

$ wget https://raw.githubusercontent.com/AppImage/pkg2appimage/master/pkg2appimage
$ chmod +x pkg2appimage
$ ./pkg2appimage testrecipe.yml

変換に成功すれば、outディレクトリ内にAppImageファイルが作成されているはずです。

$ ls out/
AppImage_Test-0.1.0.glibc2.39-x86_64.AppImage

これを実行すれば用意した画像を表示するシンプルなウィンドウが表示されます。

AppImageの動作

AppImageの特徴は、実行時に FUSEを用いて、動作に必要なファイル群を一時的にマウントして展開することです。その動作を見るためには、作成された実行ファイルに --appimage-mount オプションを付けて実行します。

$ ./AppImage_Test-0.1.0.glibc2.39-x86_64.AppImage --appimage-mount

これを実行すると /tmp/.mount_XXXXX のようなディレクトリ内に、実行中そのAppImageが使うファイルがマウントされていることが確認できます。今回の例として作成したアプリケーションで、画像が /usr/share/pixmaps/appimage-test.png にあるのにソースコード上は相対パスを使ってアクセスしているのは、実行時欲しい画像が実際には /tmp/.mount_XXXXX に展開されているので、それにアクセスできるようにするためです。ここはアプリケーションの実装時に気を付けないといけない点です。また、デフォルトでは一部の基本的なライブラリ(libcなど)はパッケージに含まれず、実行OS側に存在しなければなりません。libfuseもインストールされている必要があります。

カスタムのAppRun

AppImageパッケージを起動するとき、まずAppRunというファイルが実行されます。対象となるアプリケーションによっては、カスタムのAppRunを用意してやる必要があります。その場合は、レシピの script にAppRunを作成する処理を記述します。元のdebパッケージがdesktopやアイコンファイルを含んでいない場合、ここで作成処理を追加することもできます。

app: appimage-test

ingredients:
  dist: noble
  sources:
    - deb http://archive.ubuntu.com/ubuntu/ noble main universe
  debs:
    - /path/to/appimage-test_0.1.0-1_amd64.deb

script:
  - cat > AppRun <<\EOF
  - #!/bin/sh
  - HERE="$(dirname "$(readlink -f "${0}")")"
  - export LD_LIBRARY_PATH="${HERE}/usr/lib:${HERE}/usr/lib/x86_64-linux-gnu:${LD_LIBRARY_PATH}"
  - exec "${HERE}/usr/bin/appimage-test" "$@"
  - EOF
  - chmod a+x AppRun

スクリプト内で LD_LIBRARY_PATH などの環境変数を設定し、最後に配置した実行ファイルを呼び出します。アプリケーションに合わせて設定する環境変数を変えましょう。また、デフォルトのAppRunに任せると、カレントディレクトリを変更されてしまうので、相対パスを引数に受け付けるCLIツールをAppImage化する場合はカスタムのAppRunを用意する方が無難です。

まとめ

AppImageはLinuxで開発していると興味深い技術なのですが、パッケージ化するとなると日本語の情報がほとんど無いので今回取り上げてみました。いろいろ苦労させられることの多いLinuxでのアプリケーション配布における1つの選択肢として覚えておくのは良いかと思います。