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 [email protected]:/srv/flatpak/
ssh [email protected] "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/ [email protected]:/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选项。