强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Flatpak 应用打包完整教程 / 第 11 章:Docker 与 Flatpak

第 11 章:Docker 与 Flatpak

本章目标:掌握在 Docker 容器中构建 Flatpak 应用的方法,实现完整的 CI/CD 流水线。


11.1 为什么用 Docker 构建 Flatpak

11.1.1 动机

优势说明
可复现环境Docker 镜像确保每次构建环境一致
CI/CD 友好与主流 CI/CD 平台无缝集成
无需专用机器不需要维护专门的 Flatpak 构建服务器
并行构建多个应用可在独立容器中并行构建
跨平台可在 macOS/Windows 的 Docker 中构建(有限制)

11.1.2 限制

限制说明解决方案
无 GUI 测试容器中无图形界面使用虚拟帧缓冲 (Xvfb) 或忽略 GUI 测试
FUSE 限制flatpak-builder 需要 FUSE使用 --disable-rofiles-fuse
特权模式某些操作需要特权使用 --privileged--cap-add
镜像大小Flatpak SDK 镜像较大使用多阶段构建或专用镜像

11.2 专用构建镜像

11.2.1 官方镜像

社区提供了预配置的 Flatpak 构建镜像:

# 官方 Flatpak GitHub Actions 镜像
# 基于 Fedora,预装了 flatpak-builder 和常用 SDK
docker pull bilelmoussaoui/flatpak-github-actions:gnome-47
docker pull bilelmoussaoui/flatpak-github-actions:gnome-46
docker pull bilelmoussaoui/flatpak-github-actions:kde-6.8

# 查看镜像内容
docker run --rm bilelmoussaoui/flatpak-github-actions:gnome-47 \
    flatpak list --runtime

11.2.2 自定义构建镜像

# Dockerfile.flatpak-builder
FROM fedora:40

# 安装基本工具
RUN dnf install -y \
    flatpak \
    flatpak-builder \
    ostree \
    git \
    python3-pip \
    jq \
    && dnf clean all

# 添加 Flathub 仓库
RUN flatpak remote-add --if-not-exists flathub \
    https://dl.flathub.org/repo/flathub.flatpakrepo

# 安装 Freedesktop 运行时和 SDK
RUN flatpak install -y flathub \
    org.freedesktop.Platform//24.08 \
    org.freedesktop.Sdk//24.08

# 安装 GNOME 运行时和 SDK(可选)
RUN flatpak install -y flathub \
    org.gnome.Platform//47 \
    org.gnome.Sdk//47

# 安装 KDE 运行时和 SDK(可选)
RUN flatpak install -y flathub \
    org.kde.Platform//6.8 \
    org.kde.Sdk//6.8

# 清理缓存
RUN rm -rf /var/lib/flatpak/repo/refs/remotes/flathub/* 2>/dev/null || true

# 设置工作目录
WORKDIR /build

# 入口点
ENTRYPOINT ["flatpak-builder"]
CMD ["--help"]
# 构建镜像
docker build -f Dockerfile.flatpak-builder -t my-flatpak-builder .

# 测试镜像
docker run --rm my-flatpak-builder --version

11.2.3 最小化构建镜像

只包含 Freedesktop SDK 的最小镜像:

# Dockerfile.flatpak-minimal
FROM fedora:40-minimal

RUN microdnf install -y \
    flatpak \
    flatpak-builder \
    ostree \
    git \
    ca-certificates \
    && microdnf clean all

RUN flatpak remote-add --if-not-exists flathub \
    https://dl.flathub.org/repo/flathub.flatpakrepo && \
    flatpak install -y flathub \
    org.freedesktop.Platform//24.08 \
    org.freedesktop.Sdk//24.08 && \
    rm -rf /tmp/*

WORKDIR /build
ENTRYPOINT ["flatpak-builder"]

11.3 在 Docker 中构建 Flatpak

11.3.1 基本构建流程

# 步骤 1:准备项目文件
project/
├── com.example.App.json
├── src/
│   └── main.c
└── Dockerfile

# 步骤 2:运行构建
docker run --rm \
    -v "$(pwd):/build:ro" \
    -v "$(pwd)/output:/output" \
    --privileged \
    bilelmoussaoui/flatpak-github-actions:gnome-47 \
    bash -c "
        flatpak-builder \
            --force-clean \
            --repo=/output/repo \
            /tmp/builddir \
            /build/com.example.App.json &&
        flatpak build-bundle \
            /output/repo \
            /output/com.example.App.flatpak \
            com.example.App stable
    "

11.3.2 使用 –disable-rofiles-fuse

在 Docker 中,FUSE 可能不可用。需要禁用 rofiles-fuse:

docker run --rm \
    -v "$(pwd):/build:ro" \
    -v "$(pwd)/output:/output" \
    bilelmoussaoui/flatpak-github-actions:gnome-47 \
    flatpak-builder \
        --force-clean \
        --disable-rofiles-fuse \
        --repo=/output/repo \
        /tmp/builddir \
        /build/com.example.App.json

11.3.3 使用 BuildKit 缓存

# 使用 Docker BuildKit 缓存 Flatpak 构建
DOCKER_BUILDKIT=1 docker build \
    --output type=local,dest=output \
    --build-arg BUILDKIT_INLINE_CACHE=1 \
    .

11.3.4 多阶段构建

# Dockerfile - 多阶段构建 Flatpak 应用

# 阶段 1:构建
FROM bilelmoussaoui/flatpak-github-actions:gnome-47 AS builder

COPY . /build
WORKDIR /build

RUN flatpak-builder \
    --force-clean \
    --disable-rofiles-fuse \
    --repo=/repo \
    /tmp/builddir \
    com.example.App.json && \
    flatpak build-bundle \
    /repo \
    /app.flatpak \
    com.example.App stable

# 阶段 2:导出
FROM scratch AS output
COPY --from=builder /app.flatpak /com.example.App.flatpak
COPY --from=builder /repo /repo
# 使用多阶段构建导出 bundle
DOCKER_BUILDKIT=1 docker build \
    --output type=local,dest=output \
    .

11.4 CI/CD 集成

11.4.1 GitHub Actions 完整流水线

# .github/workflows/flatpak-release.yml
name: Flatpak Release

on:
  push:
    tags:
      - 'v*'

env:
  APP_ID: com.example.MyApp
  FLATPAK_BRANCH: stable

jobs:
  # Job 1: 验证
  validate:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    
    - name: Validate Manifest
      run: |
        jq empty ${{ env.APP_ID }}.json
        echo "✓ Manifest JSON 语法正确"
    
    - name: Validate AppStream
      run: |
        sudo apt-get install -y appstream
        appstreamcli validate --pedantic ${{ env.APP_ID }}.metainfo.xml || true
    
    - name: Check Permissions
      run: |
        FINISH_ARGS=$(jq -r '."finish-args"[]' ${{ env.APP_ID }}.json)
        echo "应用权限:"
        echo "$FINISH_ARGS"
        
        # 检查危险权限
        if echo "$FINISH_ARGS" | grep -q "filesystem=host"; then
          echo "⚠️ 警告: 应用有 host 文件系统访问权限"
        fi

  # Job 2: 构建 (x86_64)
  build-x86_64:
    needs: validate
    runs-on: ubuntu-latest
    container:
      image: bilelmoussaoui/flatpak-github-actions:gnome-47
      options: --privileged
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Build Flatpak
      uses: nickvdp/flatpak-github-actions/flatpak-builder@v4
      with:
        manifest-path: ${{ env.APP_ID }}.json
        bundle: ${{ env.APP_ID }}.flatpak
        cache-key: flatpak-builder-${{ github.sha }}
    
    - name: Test Installation
      run: |
        flatpak install --user ${{ env.APP_ID }}.flatpak
        flatpak info ${{ env.APP_ID }}
        flatpak info --show-permissions ${{ env.APP_ID }}
    
    - name: Upload Artifact
      uses: actions/upload-artifact@v4
      with:
        name: flatpak-x86_64
        path: ${{ env.APP_ID }}.flatpak

  # Job 3: 构建 (aarch64)
  build-aarch64:
    needs: validate
    runs-on: ubuntu-latest
    container:
      image: bilelmoussaoui/flatpak-github-actions:gnome-47
      options: --privileged
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Build Flatpak
      uses: nickvdp/flatpak-github-actions/flatpak-builder@v4
      with:
        manifest-path: ${{ env.APP_ID }}.json
        bundle: ${{ env.APP_ID }}-aarch64.flatpak
        cache-key: flatpak-builder-${{ github.sha }}-aarch64
        arch: aarch64
    
    - name: Upload Artifact
      uses: actions/upload-artifact@v4
      with:
        name: flatpak-aarch64
        path: ${{ env.APP_ID }}-aarch64.flatpak

  # Job 4: 发布
  release:
    needs: [build-x86_64, build-aarch64]
    runs-on: ubuntu-latest
    if: startsWith(github.ref, 'refs/tags/')
    
    steps:
    - name: Download Artifacts
      uses: actions/download-artifact@v4
    
    - name: Create Release
      uses: softprops/action-gh-release@v2
      with:
        files: |
          flatpak-x86_64/${{ env.APP_ID }}.flatpak
          flatpak-aarch64/${{ env.APP_ID }}-aarch64.flatpak
        generate_release_notes: true

11.4.2 GitLab CI 完整流水线

# .gitlab-ci.yml
stages:
  - validate
  - build
  - test
  - deploy

variables:
  APP_ID: "com.example.MyApp"

validate:
  stage: validate
  image: python:3.12-slim
  script:
    - pip install jsonschema pyyaml
    - python -m json.tool ${APP_ID}.json
    - echo "✓ Manifest 验证通过"

build:
  stage: build
  image: bilelmoussaoui/flatpak-github-actions:gnome-47
  script:
    - flatpak-builder --force-clean --disable-rofiles-fuse --repo=repo builddir ${APP_ID}.json
    - flatpak build-bundle repo ${APP_ID}.flatpak ${APP_ID} stable
  artifacts:
    paths:
      - ${APP_ID}.flatpak
    expire_in: 1 week

test:
  stage: test
  image: bilelmoussaoui/flatpak-github-actions:gnome-47
  needs: [build]
  script:
    - flatpak install --user ${APP_ID}.flatpak
    - flatpak info --show-permissions ${APP_ID}
    - timeout 10 flatpak run ${APP_ID} --version 2>/dev/null || true
    - echo "✓ 测试通过"

deploy:
  stage: deploy
  image: curlimages/curl:latest
  needs: [test]
  only:
    - tags
  script:
    - |
      curl --header "JOB-TOKEN: ${CI_JOB_TOKEN}" \
           --upload-file ${APP_ID}.flatpak \
           "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/flatpak/${CI_COMMIT_TAG}/${APP_ID}.flatpak"

11.4.3 自建 CI 服务器

#!/bin/bash
# ci-server.sh - 自建 Flatpak CI 服务器

# 使用 Jenkins Pipeline 语法
cat > Jenkinsfile << 'GROOVY'
pipeline {
    agent {
        docker {
            image 'bilelmoussaoui/flatpak-github-actions:gnome-47'
            args '--privileged'
        }
    }
    
    stages {
        stage('Validate') {
            steps {
                sh 'jq empty com.example.App.json'
                sh 'appstreamcli validate com.example.App.metainfo.xml || true'
            }
        }
        
        stage('Build') {
            steps {
                sh '''
                    flatpak-builder \
                        --force-clean \
                        --disable-rofiles-fuse \
                        --repo=repo \
                        builddir \
                        com.example.App.json
                '''
            }
        }
        
        stage('Test') {
            steps {
                sh '''
                    flatpak build-bundle repo com.example.App.flatpak com.example.App stable
                    flatpak install --user com.example.App.flatpak
                    flatpak info com.example.App
                '''
            }
        }
        
        stage('Deploy') {
            steps {
                sh '''
                    # 复制到分发服务器
                    scp com.example.App.flatpak deploy@flatpak.example.com:/srv/flatpak/
                    ssh deploy@flatpak.example.com "flatpak build-update-repo /srv/flatpak/repo"
                '''
            }
        }
    }
    
    post {
        always {
            archiveArtifacts artifacts: '*.flatpak', fingerprint: true
        }
    }
}
GROOVY

11.5 测试自动化

11.5.1 使用 Xvfb 进行 GUI 测试

#!/bin/bash
# test-gui.sh - 在无头环境中测试 Flatpak GUI 应用

APP_ID="$1"

# 安装 Xvfb
sudo apt-get install -y xvfb

# 启动虚拟显示器
Xvfb :99 -screen 0 1024x768x24 &
export DISPLAY=:99

# 安装应用
flatpak install --user -y "$APP_ID.flatpak"

# 启动应用
flatpak run "$APP_ID" &
APP_PID=$!

# 等待应用启动
sleep 5

# 检查应用是否在运行
if kill -0 $APP_PID 2>/dev/null; then
    echo "✓ 应用启动成功"
    
    # 截图(可选)
    import -window root /tmp/screenshot.png
    
    # 终止应用
    flatpak kill "$APP_ID"
    echo "✓ 应用正常退出"
else
    echo "✗ 应用启动失败"
    exit 1
fi

# 清理
kill %1  # 终止 Xvfb

11.5.2 集成测试脚本

#!/bin/bash
# integration-test.sh - Flatpak 应用集成测试

APP_ID="$1"
TESTS_PASSED=0
TESTS_FAILED=0

assert() {
    local description="$1"
    local command="$2"
    
    if eval "$command" &>/dev/null; then
        echo "  ✓ $description"
        TESTS_PASSED=$((TESTS_PASSED + 1))
    else
        echo "  ✗ $description"
        TESTS_FAILED=$((TESTS_FAILED + 1))
    fi
}

echo "=== 集成测试: $APP_ID ==="

echo ""
echo "--- 安装测试 ---"
assert "应用可安装" "flatpak install --user -y $APP_ID.flatpak"
assert "应用已注册" "flatpak info $APP_ID"

echo ""
echo "--- 权限测试 ---"
assert "有 Wayland 权限" "flatpak info --show-permissions $APP_ID | grep -q wayland"
assert "无 host 文件系统权限" "! flatpak info --show-permissions $APP_ID | grep -q 'filesystem=host'"

echo ""
echo "--- 运行测试 ---"
assert "可显示版本信息" "timeout 10 flatpak run $APP_ID --version 2>/dev/null"
assert "可正常退出" "timeout 5 flatpak run $APP_ID --help 2>/dev/null || true"

echo ""
echo "--- 卸载测试 ---"
assert "应用可卸载" "flatpak uninstall -y $APP_ID"
assert "应用已移除" "! flatpak list --app | grep -q $APP_ID"

echo ""
echo "=== 结果: $TESTS_PASSED 通过, $TESTS_FAILED 失败 ==="
[ $TESTS_FAILED -eq 0 ] && exit 0 || exit 1

11.6 发布自动化

11.6.1 自动发布到私有仓库

#!/bin/bash
# publish-to-repo.sh - 自动发布到私有 Flatpak 仓库

set -e

APP_ID="$1"
VERSION="$2"
REPO_DIR="/srv/flatpak-repo"
BUILD_DIR="builddir"

echo "=== 发布 $APP_ID v$VERSION ==="

# 1. 构建
echo "1. 构建..."
flatpak-builder \
    --force-clean \
    --repo=repo \
    "$BUILD_DIR" \
    "${APP_ID}.json"

# 2. 导出到仓库
echo "2. 导出到仓库..."
flatpak build-export "$REPO_DIR" "$BUILD_DIR" stable

# 3. 更新仓库摘要
echo "3. 更新仓库摘要..."
flatpak build-update-repo "$REPO_DIR"

# 4. 生成 Delta
echo "4. 生成 Delta..."
flatpak build-update-repo --generate-static-deltas "$REPO_DIR"

# 5. 生成 Bundle
echo "5. 生成 Bundle..."
flatpak build-bundle "$REPO_DIR" "${APP_ID}-${VERSION}.flatpak" "$APP_ID" stable

# 6. 验证
echo "6. 验证..."
flatpak remote-add --no-gpg-verify --if-not-exists local-repo "$REPO_DIR"
flatpak install -y local-repo "$APP_ID"
flatpak info "$APP_ID"

echo ""
echo "=== 发布完成 ==="
echo "仓库: $REPO_DIR"
echo "Bundle: ${APP_ID}-${VERSION}.flatpak"

11.6.2 Docker Compose 自动化

# docker-compose.flatpak.yml
version: '3.8'

services:
  flatpak-builder:
    image: bilelmoussaoui/flatpak-github-actions:gnome-47
    privileged: true
    volumes:
      - .:/build:ro
      - flatpak-output:/output
      - flatpak-cache:/root/.local/share/flatpak-builder
    command: >
      bash -c "
        flatpak-builder \
          --force-clean \
          --disable-rofiles-fuse \
          --ccache \
          --repo=/output/repo \
          /tmp/builddir \
          /build/com.example.App.json &&
        flatpak build-bundle \
          /output/repo \
          /output/com.example.App.flatpak \
          com.example.App stable
      "
  
  flatpak-repo:
    image: nginx:alpine
    ports:
      - "8080:80"
    volumes:
      - flatpak-output:/usr/share/nginx/html:ro
    depends_on:
      - flatpak-builder

volumes:
  flatpak-output:
  flatpak-cache:
# 运行构建
docker compose -f docker-compose.flatpak.yml up

# 访问仓库
curl http://localhost:8080/repo/

11.7 高级技巧

11.7.1 使用 Docker 多平台构建

#!/bin/bash
# build-multiarch-docker.sh

APP_ID="com.example.MyApp"
ARCHS=("x86_64" "aarch64")

for arch in "${ARCHS[@]}"; do
    echo "构建 ${arch} 架构..."
    
    docker run --rm \
        --platform "linux/${arch}" \
        -v "$(pwd):/build:ro" \
        -v "$(pwd)/output-${arch}:/output" \
        --privileged \
        bilelmoussaoui/flatpak-github-actions:gnome-47 \
        flatpak-builder \
            --force-clean \
            --disable-rofiles-fuse \
            --arch="${arch}" \
            --repo=/output/repo \
            /tmp/builddir \
            /build/${APP_ID}.json
    
    docker run --rm \
        -v "$(pwd)/output-${arch}:/output" \
        bilelmoussaoui/flatpak-github-actions:gnome-47 \
        flatpak build-bundle \
            /output/repo \
            /output/${APP_ID}-${arch}.flatpak \
            ${APP_ID} stable
    
    echo "✓ ${arch} 构建完成"
done

11.7.2 使用 Docker Buildx

# 使用 Docker Buildx 进行多平台构建
docker buildx build \
    --platform linux/amd64,linux/arm64 \
    --output type=local,dest=output \
    -f Dockerfile.flatpak \
    .

11.7.3 缓存优化

# GitHub Actions 缓存优化
- name: Cache Flatpak Builder
  uses: actions/cache@v4
  with:
    path: |
      ~/.local/share/flatpak-builder/git
      ~/.local/share/flatpak-builder/downloads
    key: flatpak-builder-${{ hashFiles('*.json') }}
    restore-keys: |
      flatpak-builder-

11.8 业务场景

场景 1:企业级自动发布流水线

# .github/workflows/enterprise-flatpak.yml
name: Enterprise Flatpak Pipeline

on:
  push:
    branches: [main]
    tags: ['v*']

jobs:
  build-and-publish:
    runs-on: ubuntu-latest
    container:
      image: enterprise/flatpak-builder:latest  # 企业自定义镜像
      options: --privileged
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Build
      run: |
        flatpak-builder --force-clean --disable-rofiles-fuse \
            --repo=repo builddir com.example.App.json
    
    - name: Security Audit
      run: |
        # 权限审计
        jq '."finish-args"[]' com.example.App.json | while read perm; do
          if echo "$perm" | grep -qE "(host|device=all|system-talk)"; then
            echo "::error::危险权限: $perm"
            exit 1
          fi
        done
    
    - name: Publish to Internal Repo
      run: |
        flatpak build-export repo builddir stable
        flatpak build-update-repo repo
        
        # 同步到企业仓库
        rsync -avz repo/ deploy@flatpak.internal.com:/srv/repo/
    
    - name: Notify
      if: success()
      run: |
        curl -X POST "$SLACK_WEBHOOK" \
            -d "{\"text\":\"Flatpak 发布成功: com.example.App v${GITHUB_REF_NAME}\"}"

场景 2:开源项目持续集成

# .github/workflows/flatpak-ci.yml
name: Flatpak CI

on: [push, pull_request]

jobs:
  flatpak:
    runs-on: ubuntu-latest
    container:
      image: bilelmoussaoui/flatpak-github-actions:gnome-47
      options: --privileged
    
    steps:
    - uses: actions/checkout@v4
    
    - uses: nickvdp/flatpak-github-actions/flatpak-builder@v4
      with:
        manifest-path: com.example.App.json
        cache-key: flatpak-${{ github.sha }}
    
    - name: Smoke Test
      run: |
        flatpak install --user flatpak-builder/com.example.App.flatpak
        flatpak run com.example.App --help

11.9 注意事项

⚠️ 特权模式安全
Docker 的 --privileged 模式会授予容器几乎无限的权限。仅在受信任的 CI/CD 环境中使用。

⚠️ 镜像大小
包含完整 GNOME SDK 的 Docker 镜像约 5-8 GB。使用多阶段构建或及时清理缓存。

⚠️ 网络限制
某些 CI/CD 环境限制了网络访问。确保 Manifest 中声明了所有依赖,使用 --disable-download 进行离线构建。

⚠️ FUSE 兼容性
Docker 默认不支持 FUSE。使用 --disable-rofiles-fuse 选项。


11.10 扩展阅读