11 - Docker 构建与部署
第 11 章:Docker 构建与部署
本章介绍如何使用 Docker 构建、测试和部署 Vala 应用,以及如何在容器环境中运行 GTK 应用。
11.1 为什么使用 Docker
11.1.1 Docker 在 Vala 开发中的价值
| 场景 | 问题 | Docker 解决方案 |
|---|---|---|
| 环境一致性 | 不同机器上 valac 版本不同 | 统一的编译环境 |
| CI/CD | 需要自动化构建 | 可复现的构建镜像 |
| 交叉编译 | 目标平台不可用 | 多架构构建 |
| 依赖管理 | GTK/GLib 版本碎片化 | 固定版本的依赖 |
| 测试隔离 | 测试影响本地环境 | 每次干净的测试环境 |
| 分发 | 用户安装困难 | Flatpak 或容器化分发 |
11.1.2 典型工作流
开发者机器 Docker 构建 CI/CD 分发
┌──────────┐ ┌──────────────┐ ┌──────────┐ ┌──────────┐
│ .vala │────→│ valac 编译 │────→│ 自动测试 │────→│ Flatpak │
│ meson │ │ Meson 构建 │ │ 代码检查 │ │ Container│
│ build │ │ 生成制品 │ │ 打包发布 │ │ Package │
└──────────┘ └──────────────┘ └──────────┘ └──────────┘
11.2 基本 Dockerfile
11.2.1 Ubuntu 基础镜像
# Dockerfile.ubuntu
# 基于 Ubuntu 的 Vala 开发环境
FROM ubuntu:24.04
# 避免交互式安装
ENV DEBIAN_FRONTEND=noninteractive
# 安装依赖
RUN apt-get update && apt-get install -y \
valac \
libglib2.0-dev \
libgtk-4-dev \
libadwaita-1-dev \
meson \
ninja-build \
pkg-config \
git \
&& rm -rf /var/lib/apt/lists/*
# 设置工作目录
WORKDIR /app
# 复制项目文件
COPY . .
# 构建
RUN meson setup build \
&& ninja -C build
# 运行测试(如果有)
RUN ninja -C build test || true
# 安装到 /opt
RUN ninja -C build install
# 入口点
CMD ["/app/build/src/myapp"]
11.2.2 Fedora 基础镜像
# Dockerfile.fedora
FROM fedora:40
# 安装依赖
RUN dnf install -y \
vala \
glib2-devel \
gtk4-devel \
libadwaita-devel \
meson \
ninja-build \
pkgconfig \
gcc \
&& dnf clean all
WORKDIR /app
COPY . .
RUN meson setup build \
&& ninja -C build \
&& ninja -C build test
CMD ["/app/build/src/myapp"]
11.2.3 Alpine 基础镜像(轻量级)
# Dockerfile.alpine
# 适合纯 Vala 项目(不使用 GTK)
FROM alpine:3.20
RUN apk add --no-cache \
vala \
glib-dev \
gobject-introspection-dev \
meson \
ninja \
gcc \
musl-dev
WORKDIR /app
COPY . .
RUN meson setup build \
-Dgtk=false \
&& ninja -C build
CMD ["/app/build/src/myapp"]
11.2.4 多阶段构建(减小镜像体积)
# Dockerfile.multistage
# 阶段 1:构建
FROM ubuntu:24.04 AS builder
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y \
valac \
libglib2.0-dev \
libgtk-4-dev \
libadwaita-1-dev \
meson \
ninja-build \
pkg-config \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY . .
RUN meson setup build --prefix=/usr \
-Dbuildtype=release \
&& ninja -C build
# 阶段 2:运行(只包含运行时依赖)
FROM ubuntu:24.04
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y \
libglib2.0-0 \
libgtk-4-1 \
libadwaita-1-0 \
&& rm -rf /var/lib/apt/lists/*
# 从构建阶段复制编译产物
COPY --from=builder /app/build/src/myapp /usr/local/bin/myapp
CMD ["myapp"]
11.3 Meson 集成
11.3.1 Docker Compose 开发环境
# docker-compose.yml
version: '3.8'
services:
# 开发环境
dev:
build:
context: .
dockerfile: Dockerfile.dev
volumes:
- .:/app
- build-cache:/app/build
environment:
- DISPLAY=${DISPLAY}
network_mode: host
stdin_open: true
tty: true
# 构建服务
build:
build:
context: .
dockerfile: Dockerfile.ubuntu
volumes:
- ./build-output:/app/build
# 测试服务
test:
build:
context: .
dockerfile: Dockerfile.test
command: ninja -C build test
volumes:
build-cache:
11.3.2 开发用 Dockerfile
# Dockerfile.dev
FROM ubuntu:24.04
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y \
valac \
libglib2.0-dev \
libgtk-4-dev \
libadwaita-1-dev \
meson \
ninja-build \
pkg-config \
gdb \
valgrind \
git \
vim \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
# 进入开发环境
CMD ["bash"]
# 使用方法
docker compose run --rm dev
# 在容器内:
meson setup build
ninja -C build
./build/src/myapp
11.4 CI/CD 集成
11.4.1 GitLab CI
# .gitlab-ci.yml
stages:
- build
- test
- package
variables:
UBUNTU_IMAGE: ubuntu:24.04
# 构建阶段
build:
stage: build
image: ${UBUNTU_IMAGE}
before_script:
- apt-get update
- apt-get install -y valac libglib2.0-dev libgtk-4-dev
libadwaita-1-dev meson ninja-build pkg-config
script:
- meson setup build --prefix=/usr
- ninja -C build
artifacts:
paths:
- build/
expire_in: 1 hour
# 测试阶段
test:
stage: test
image: ${UBUNTU_IMAGE}
before_script:
- apt-get update
- apt-get install -y valac libglib2.0-dev libgtk-4-dev
libadwaita-1-dev meson ninja-build pkg-config xvfb
script:
- meson setup build --prefix=/usr
- ninja -C build
- xvfb-run ninja -C build test
dependencies:
- build
# 打包阶段
package:
stage: package
image: ${UBUNTU_IMAGE}
script:
- meson setup build --prefix=/usr
- ninja -C build
- DESTDIR=install ninja -C build install
- tar czf myapp.tar.gz -C install .
artifacts:
paths:
- myapp.tar.gz
expire_in: 1 week
only:
- tags
11.4.2 GitHub Actions
# .github/workflows/build.yml
name: Build and Test
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
container:
image: ubuntu:24.04
steps:
- name: 安装依赖
run: |
apt-get update
apt-get install -y \
valac libglib2.0-dev libgtk-4-dev \
libadwaita-1-dev meson ninja-build \
pkg-config xvfb git
- name: 检出代码
uses: actions/checkout@v4
- name: 配置构建
run: meson setup build --prefix=/usr
- name: 编译
run: ninja -C build
- name: 运行测试
run: xvfb-run ninja -C build test
- name: 上传构建产物
uses: actions/upload-artifact@v4
with:
name: build-output
path: build/
11.5 交叉编译
11.5.1 ARM64 交叉编译
# Dockerfile.cross-arm64
FROM ubuntu:24.04
ENV DEBIAN_FRONTEND=noninteractive
# 安装交叉编译工具链
RUN apt-get update && apt-get install -y \
gcc-aarch64-linux-gnu \
g++-aarch64-linux-gnu \
valac \
meson \
ninja-build \
pkg-config \
&& rm -rf /var/lib/apt/lists/*
# 安装 ARM64 架构的库
RUN dpkg --add-architecture arm64 \
&& apt-get update \
&& apt-get install -y \
libglib2.0-dev:arm64 \
libgtk-4-dev:arm64 \
libadwaita-1-dev:arm64 \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY . .
# 创建 Meson 交叉编译文件
RUN cat > /tmp/cross-arm64.ini << 'EOF'
[binaries]
c = 'aarch64-linux-gnu-gcc'
cpp = 'aarch64-linux-gnu-g++'
ar = 'aarch64-linux-gnu-ar'
strip = 'aarch64-linux-gnu-strip'
pkgconfig = 'pkg-config'
[host_machine]
system = 'linux'
cpu_family = 'aarch64'
cpu = 'aarch64'
endian = 'little'
[properties]
needs_exe_wrapper = true
EOF
# 交叉编译
RUN meson setup build \
--cross-file /tmp/cross-arm64.ini \
--prefix=/usr \
&& ninja -C build
CMD ["bash"]
11.5.2 使用 QEMU 运行 ARM64 二进制
# 在 Docker 中运行 ARM64 二进制
docker run --rm -it \
--platform linux/arm64 \
ubuntu:24.04 \
/path/to/myapp
11.5.3 多架构构建(Docker Buildx)
# Dockerfile.multiarch
FROM --platform=$BUILDPLATFORM ubuntu:24.04 AS builder
ENV DEBIAN_FRONTEND=noninteractive
ARG TARGETPLATFORM
ARG TARGETARCH
RUN apt-get update && apt-get install -y \
valac meson ninja-build pkg-config \
gcc \
&& rm -rf /var/lib/apt/lists/*
# 根据目标架构安装对应的库
RUN if [ "$TARGETARCH" = "arm64" ]; then \
dpkg --add-architecture arm64 && \
apt-get update && \
apt-get install -y \
gcc-aarch64-linux-gnu \
libglib2.0-dev:arm64; \
else \
apt-get update && \
apt-get install -y \
libglib2.0-dev; \
fi
WORKDIR /app
COPY . .
RUN meson setup build && ninja -C build
# 运行阶段
FROM ubuntu:24.04
COPY --from=builder /app/build/src/myapp /usr/local/bin/
CMD ["myapp"]
# 构建多架构镜像
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t myapp:latest \
--push .
11.6 GTK 应用容器化
11.6.1 运行 GTK 应用(X11 转发)
# Dockerfile.gtk-app
FROM ubuntu:24.04
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y \
valac \
libglib2.0-dev \
libgtk-4-dev \
libadwaita-1-dev \
meson \
ninja-build \
pkg-config \
# X11 相关
libx11-6 \
libx11-xcb1 \
libxcb1 \
libgl1-mesa-glx \
libegl1-mesa \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY . .
RUN meson setup build && ninja -C build
# 使用 X11 socket
CMD ["./build/src/myapp"]
# 允许 Docker 访问 X11
xhost +local:docker
# 运行 GTK 应用
docker run --rm -it \
-e DISPLAY=$DISPLAY \
-v /tmp/.X11-unix:/tmp/.X11-unix \
myapp:latest
# 使用 Wayland
docker run --rm -it \
-e WAYLAND_DISPLAY=$WAYLAND_DISPLAY \
-v $XDG_RUNTIME_DIR/$WAYLAND_DISPLAY:/tmp/$WAYLAND_DISPLAY \
myapp:latest
11.6.2 使用 PipeWire/PulseAudio(音频)
# 运行带音频支持的容器
docker run --rm -it \
-e PULSE_SERVER=unix:/run/user/1000/pulse/native \
-v /run/user/1000/pulse:/run/user/1000/pulse \
-e DISPLAY=$DISPLAY \
-v /tmp/.X11-unix:/tmp/.X11-unix \
myapp:latest
11.6.3 Flatpak 打包(推荐的桌面应用分发方式)
// com.example.MyApp.json(Flatpak manifest)
{
"app-id": "com.example.MyApp",
"runtime": "org.gnome.Platform",
"runtime-version": "46",
"sdk": "org.gnome.Sdk",
"command": "myapp",
"finish-args": [
"--share=ipc",
"--socket=x11",
"--socket=wayland",
"--device=dri"
],
"modules": [
{
"name": "myapp",
"buildsystem": "meson",
"sources": [
{
"type": "git",
"url": "https://github.com/example/myapp.git"
}
]
}
]
}
# 构建 Flatpak
flatpak-builder --force-clean build-dir com.example.MyApp.json
# 安装
flatpak-builder --user --install build-dir com.example.MyApp.json
# 运行
flatpak run com.example.MyApp
11.7 测试环境
11.7.1 测试用 Dockerfile
# Dockerfile.test
FROM ubuntu:24.04
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y \
valac \
libglib2.0-dev \
libgtk-4-dev \
libadwaita-1-dev \
meson \
ninja-build \
pkg-config \
# 测试工具
xvfb \
xauth \
# 代码质量
valgrind \
cppcheck \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY . .
# 配置构建(启用测试)
RUN meson setup build \
-Dtests=true \
-Db_sanitize=address
# 编译
RUN ninja -C build
# 运行测试(使用 Xvfb 模拟显示)
CMD ["sh", "-c", "xvfb-run ninja -C build test"]
11.7.2 Valgrind 内存检测
# Dockerfile.valgrind
FROM ubuntu:24.04
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y \
valac \
libglib2.0-dev \
meson \
ninja-build \
valgrind \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY . .
RUN meson setup build \
-Db_sanitize=none \
&& ninja -C build
# 使用 Valgrind 运行
CMD ["valgrind", "--leak-check=full", "--show-leak-kinds=all", \
"./build/src/myapp"]
11.7.3 Meson 测试配置
# tests/meson.build
test_basic = executable('test_basic',
'test_basic.vala',
dependencies: [mylib_dep],
)
test('basic test', test_basic)
# 如果需要 GUI 测试
if get_option('tests')
test_gui = executable('test_gui',
'test_gui.vala',
dependencies: [gtk4_dep, libadwaita_dep],
)
test('gui test',
find_program('xvfb-run'),
args: [test_gui.full_path()],
workdir: meson.current_build_dir(),
)
endif
11.8 业务场景:完整的 CI/CD 流水线
11.8.1 完整的 GitLab CI 配置
# .gitlab-ci.yml
stages:
- lint
- build
- test
- package
- deploy
variables:
DOCKER_IMAGE: ubuntu:24.04
# 代码检查
lint:
stage: lint
image: ${DOCKER_IMAGE}
before_script:
- apt-get update && apt-get install -y valac meson ninja-build
script:
- meson setup build
- ninja -C build
- echo "代码检查通过"
allow_failure: true
# 多平台构建
.build_template: &build_template
stage: build
before_script:
- apt-get update
- apt-get install -y valac libglib2.0-dev libgtk-4-dev
libadwaita-1-dev meson ninja-build pkg-config
script:
- meson setup build --prefix=/usr
- ninja -C build
artifacts:
paths:
- build/
build:ubuntu:
<<: *build_template
image: ubuntu:24.04
build:fedora:
<<: *build_template
image: fedora:40
before_script:
- dnf install -y vala glib2-devel gtk4-devel
libadwaita-devel meson ninja-build pkgconfig
# 测试
test:
stage: test
image: ${DOCKER_IMAGE}
before_script:
- apt-get update
- apt-get install -y valac libglib2.0-dev libgtk-4-dev
libadwaita-1-dev meson ninja-build pkg-config xvfb
script:
- meson setup build --prefix=/usr -Dtests=true
- ninja -C build
- xvfb-run ninja -C build test
dependencies:
- build:ubuntu
# 打包
package:flatpak:
stage: package
image: bilelmoussaoui/flatpak-github-actions:gnome-46
script:
- flatpak-builder --force-clean build-dir com.example.MyApp.json
artifacts:
paths:
- build-dir/
only:
- tags
# 部署
deploy:pages:
stage: deploy
image: alpine:latest
script:
- mkdir public
- cp -r build-dir/repo public/
artifacts:
paths:
- public
only:
- main
11.9 注意事项
⚠️ Docker 构建常见问题
- X11 权限:运行 GUI 应用需要
xhost +local:docker - Wayland 支持:需要挂载
$XDG_RUNTIME_DIR - 镜像体积:使用多阶段构建减小最终镜像
- 缓存:利用 Docker 层缓存,先复制
meson.build再复制源码 - 架构:交叉编译时注意库的架构匹配
- 显示测试:使用
xvfb-run运行需要显示的测试
11.10 扩展阅读
| 资源 | 链接 |
|---|---|
| Docker 官方文档 | https://docs.docker.com/ |
| Flatpak 文档 | https://docs.flatpak.org/ |
| Meson Docker 支持 | https://mesonbuild.com/Cross-compilation.html |
| GNOME Flatpak 指南 | https://wiki.gnome.org/HowDoI/Flatpak |
| Docker Buildx | https://docs.docker.com/build/buildx/ |
11.11 总结
| 要点 | 说明 |
|---|---|
| 基础镜像 | Ubuntu / Fedora / Alpine |
| 多阶段构建 | 缩小镜像体积 |
| CI/CD | GitLab CI / GitHub Actions |
| 交叉编译 | Meson 交叉编译文件 |
| GTK 容器化 | X11 转发 + Wayland 支持 |
| 分发方式 | Flatpak(推荐)/ Docker |
| 测试 | xvfb-run + Valgrind |
下一章我们将学习 Vala 的最佳实践。→ 第 12 章:最佳实践