首页
/ AppFlowy Docker 镜像构建指南:从 docker-compose 命令到多阶段 Dockerfile 的源码级解析

AppFlowy Docker 镜像构建指南:从 docker-compose 命令到多阶段 Dockerfile 的源码级解析

2026-09-03 20:05:03作者:郜逊炳

本篇以 AppFlowy 仓库中 frontend/scripts/docker-buildfiles/ 目录下的 Docker 构建文档为主线,完整讲解如何一行命令构建 AppFlowy 的 Linux Docker 镜像,并结合同目录的 Dockerfiledocker-compose.yml,深入剖析双阶段构建的每一步:工具链安装、cargo make 构建链调用、UID/GID 注入,以及容器启动时依赖 X11 转发与 GPU 设备映射的运行约束。读完后你将能够独立构建该镜像、排查 cannot open display 一类常见问题,并理解 AppFlowy「Flutter 前端 + Rust 后端(dart-ffi)」在 Linux 下的产物形态。

1. 快速构建:文档给出的标准命令

README 给出了构建 AppFlowy Docker 镜像的核心命令:

docker-compose build --build-arg uid=$(id -u) --build-arg gid=$(id -g)

这条命令的三个关键要素:

  • 执行位置:由于 docker-compose.ymlbuild.context 指向仓库根目录(../../..),而 dockerfile 显式指定为 ./frontend/scripts/docker-buildfiles/Dockerfile,因此在仓库任意位置执行该命令都能正确定位构建上下文与 Dockerfile;
  • --build-arg uid=$(id -u):把宿主机当前用户的 UID 传入构建,最终镜像内的 appflowy 用户会以此 UID 创建;
  • --build-arg gid=$(id -g):同理传入 GID。

为什么要注入 UID/GID?从 Dockerfile 的 APP 阶段可以看到:

# Set up appflowy user
ARG user=appflowy
ARG uid=1000
ARG gid=1000
RUN groupadd --gid $gid $user
RUN useradd --create-home --uid $uid --gid $gid $user
USER $user

容器以 appflowy 用户运行,数据目录(工作区文件、配置等)通常位于该用户主目录下。若容器内用户 UID 与宿主机挂载卷属主不一致,会出现文件权限问题。默认值为 1000,即大多数 Linux 发行版首个普通用户的 UID;通过 $(id -u) / $(id -g) 自动匹配宿主机用户,是官方文档推荐的省事做法。

2. 构建三件套:目录结构与角色分工

frontend/scripts/docker-buildfiles/ 目录只包含三个文件,各自职责清晰:

文件 角色
Dockerfile 双阶段构建脚本:builder 阶段编译出 AppFlowy 桌面应用,APP 阶段打包出精简运行镜像
docker-compose.yml 定义 app 服务的镜像名、构建参数,以及运行时的 X11/GPU/DBus 挂载
README.md 面向用户的一行构建命令说明

docker-compose.yml 中镜像被标记为 appflowy/appflowy:latest,构建上下文是仓库根目录,这与 Dockerfile 中的 COPY . /appflowy 相呼应——整个仓库(含 Rust 源码与 Flutter 源码)都会被复制进 builder 镜像进行编译,因此构建耗时较长属于正常现象。

3. Dockerfile 深度解析:BUILDER 阶段

3.1 基础镜像与构建用户

Dockerfile 选用 Arch Linux 作为构建基础:

FROM archlinux/archlinux:base-devel as builder
RUN chown root:root /usr/bin/sudo && chmod 4755 /usr/bin/sudo

# Upgrade the system
RUN pacman -Syyu --noconfirm

# Set up makepkg user and workdir
ARG user=makepkg
RUN pacman -S --needed --noconfirm sudo
RUN useradd --system --create-home $user && \
    echo "$user ALL=(ALL:ALL) NOPASSWD:ALL" >> /etc/sudoers

要点:

  • base-devel 自带 gcc 等基础开发包,后续再用 pacman 补齐 clang、cmake、ninja、pkg-config 等 Rust/Flutter 工具链依赖(第 21 行);
  • 创建免密码 sudo 的 makepkg 系统用户,编译过程以该非 root 用户执行,避免产物权限混乱;
  • ENV PATH 预先注入了 flutter/bindart-sdk/bin 和 pub-cache 路径,保证后续 RUN 中裸写 flutterdartcargo 均可用。

3.2 工具链版本钉死

构建镜像里所有关键工具都做了精确版本固定,保证构建可复现:

工具 版本 安装方式(对应 Dockerfile 行号)
Rust toolchain 1.81(rustup toolchain install 1.81 Dockerfile#L23-L26
Flutter 3.27.4-stable(官方 tar 包直接下载解压) Dockerfile#L29-L36
protoc_plugin(Dart 全局包) 21.1.2 Dockerfile#L37
cargo-make 0.37.18(cargo install --locked Dockerfile#L42
cargo-binstall 1.10.17 Dockerfile#L43
duckscript_cli 由 cargo binstall 安装(版本由 lock 机制确定) Dockerfile#L44

注意一点版本差异:仓库 rust-toolchain.toml 声明的当前开发渠道是 1.85,而 Dockerfile 里通过 rustup 显式安装并 rustup default 1.81。也就是说 Docker 构建使用的是镜像内钉死的 1.81 工具链,而非仓库 rust-toolchain 文件声明的版本——这是 Dockerfile 有意为之的版本隔离,阅读时以 Dockerfile 为准。

AppFlowy 运行期依赖的 C 库也通过 pacman 一次性装齐(Dockerfile#L40):jemalloc git libkeybinder3 sqlite clang rsync libnotify rocksdb zstd mpv,对应 Rust 后端编译所需的 SQLite、RocksDB(向量/本地存储)等原生依赖,以及 Flutter Linux 桌面运行时的 gtk3 等。

3.3 真正的编译命令:一条 cargo make 调用

构建的核心在 Dockerfile#L46-L54

COPY . /appflowy
RUN sudo chown -R $user: /appflowy
WORKDIR /appflowy
RUN cd frontend && \
    source ~/.cargo/env && \
    cargo make appflowy-flutter-deps-tools && \
    cargo make flutter_clean && \
    OPENSSL_STATIC=1 ZSTD_SYS_USE_PKG_CONFIG=1 ROCKSDB_LIB_DIR="/usr/lib/" cargo make -p production-linux-x86_64 appflowy-linux

这条命令链与 AppFlowy 仓库自带的 cargo-make 构建系统(frontend/Makefile.toml 及其 scripts/makefile 子模块)完全一致,逐步拆解:

  1. cargo make appflowy-flutter-deps-tools:定义于 env.toml#L1-L2,它委托给 install_flutter_prerequests,即「添加 Rust 目标平台(Linux 下为 x86_64-unknown-linux-gnu)+ 安装 Flutter Protobuf 工具」,是 Flutter 侧代码生成的前置条件(env.toml#L97-L98env.toml#L92-L95)。
  2. cargo make flutter_clean:定义于 tool.toml#L1-L7,依次执行 cargo clean、删除宏构建缓存、删除 Rust 与 Dart 两侧的 Protobuf 生成文件。Docker 场景下每次都从干净状态开始,保证生成代码与 code_generation 任务重新生成的产物一致。
  3. cargo make -p production-linux-x86_64 appflowy-linux-p production-linux-x86_64 是 cargo-make 的项目 profile,决定 TARGET_OS=linuxBUILD_FLAG=releaseRUST_COMPILE_TARGET=x86_64-unknown-linux-gnu 等构建变量。appflowy-linux 任务定义于 flutter.toml#L32-L41,依赖 appflowy-core-release,随后串行执行 code_generationset-app-versionflutter-buildcopy-to-productcreate-release-archive

其中 Rust 后端的编译入口是 desktop.toml 中的 appflowy-core-releasesdk-release-build 任务(desktop.toml#L97-L106):

cd rust-lib/
cargo build --profile ${CARGO_PROFILE} --package=dart-ffi --target ${RUST_COMPILE_TARGET} --features "${FLUTTER_DESKTOP_FEATURES}"

即 release 构建 frontend/rust-lib/dart-ffi 这个 FFI 门面 crate(它把 flowy-core、flowy-user、flowy-folder、flowy-database2 等模块经 lib-dispatch 暴露给 Dart)。构建成功后 post-desktop-linux 会把产物 libxxx.sobinding.h 拷贝到 appflowy_flutter/linux/flutter/dart_ffidesktop.toml#L161-L178),随后 flutter build linux --releaseflutter.toml#L240-L247)把整个桌面应用打进 appflowy_flutter/build/linux/x64/release/bundle

三个环境变量也值得注意:

  • OPENSSL_STATIC=1:让 rust-openssl 以静态方式链接 OpenSSL(pacman 已装好 openssl,避免动态库路径问题);
  • ZSTD_SYS_USE_PKG_CONFIG=1:让 zstd-sys crate 通过 pkg-config 复用系统 zstd,而不是走 CMake 源码编译;
  • ROCKSDB_LIB_DIR="/usr/lib/":指引 rocksdb 绑定找到 pacman 安装的 librocksdb。

这三个变量本质上是把 Rust 原生依赖的构建策略对齐 Arch 系统包,缩短构建时间并减少动态链接面。

4. APP 阶段:从 2GB 级构建镜像到精简运行镜像

FROM archlinux/archlinux
RUN pacman -Syyu --noconfirm
RUN pacman -S --noconfirm xdg-user-dirs gtk3 libkeybinder3 libnotify rocksdb && \
    pacman -Scc --noconfirm

Dockerfile#L57-L84 的 APP 阶段只从 builder 镜像中拷走最终的 bundle 目录:

COPY --from=builder /appflowy/frontend/appflowy_flutter/build/linux/x64/release/bundle .
RUN xdg-user-dirs-update && \
    test -e ./AppFlowy && \
    file ./AppFlowy

CMD ["./AppFlowy"]

这里做了三件事:

  1. 只安装运行期依赖(gtk3、libkeybinder3、libnotify、rocksdb 等),编译器、Rust/Flutter 工具链全部留在 builder 层,不进最终镜像;
  2. test -e ./AppFlowy && file ./AppFlowy:构建期自检,若 Flutter 产出的 AppFlowy 可执行文件不存在,直接让 docker build 失败并暴露产物信息,而不是交付一个“能启动但缺二进制”的坏镜像;
  3. 以第 3.2 节注入 UID/GID 的 appflowy 用户启动 ./AppFlowy

这个 bundle 目录的内容,正是 3.3 节 flutter-build 任务在 flutter build linux --release 后产生的标准 Flutter Linux 发布产物(AppFlowy 启动器 + libappflowy.so + icudtl.dat 等)。

5. 运行约束:X11 转发、GPU 与 DBus

AppFlowy 是 GUI 桌面应用,Docker 运行它的前提是宿主机存在图形会话。docker-compose.yml 中的每一项挂载都有明确目的:

services:
  app:
    build:
      context: ../../..
      dockerfile: ./frontend/scripts/docker-buildfiles/Dockerfile
    image: appflowy/appflowy:latest
    stdin_open: true
    devices:
      - /dev/dri:/dev/dri # fixes MESA-LOADER error
    environment:
      - DISPLAY=$DISPLAY
      - NO_AT_BRIDGE=1 # fixes dbind-WARNING
    volumes:
      - $HOME/.Xauthority:/root/.Xauthority:rw
      - /tmp/.X11-unix:/tmp/.X11-unix
      - /dev/dri:/dev/dri
      - /var/run/dbus/system_bus_socket:/var/run/dbus/system_bus_socket
    network_mode: host

逐项解释:

  • X11 转发DISPLAY 环境变量 + 宿主机 /tmp/.X11-unix socket 挂载,是容器内 GTK 程序连上宿主机 X server 的标准手段。compose 文件头部注释特别强调:运行前必须先在宿主机执行 xhost local:docker 授权,否则会报 Gtk-WARNING **: cannot open display: :0
  • /dev/dri 设备映射(devices 与 volumes 中重复声明):把宿主机的 DRM/RenderNode 设备直通给容器,注释写明用于修复 MESA-LOADER 错误,即 GPU 硬件加速渲染;
  • NO_AT_BRIDGE=1:禁用 AT-SPI 无障碍桥,消除 dbind-WARNING 噪音(注释直接标注了用途);
  • DBus 系统总线挂载/var/run/dbus/system_bus_socket 供 GTK/GTK3 与系统总线通信;
  • network_mode: host:共享宿主机网络栈,简化网络访问(AppFlowy 若连接云端服务时走宿主网络);
  • stdin_open: true:保持容器 stdin 打开,便于终端交互调试。

一个需要留意的实现细节:volumes 把宿主机的 $HOME/.Xauthority 挂载到了容器内 /root/.Xauthority,而 APP 阶段实际以 appflowy 用户运行。这说明该挂载主要服务于 X11 认证凭据的传递路径约定;如果运行环境中 Xauthority 校验出现异常,可以从「宿主机 xhost 授权是否生效、Xauthority 文件是否可被容器进程读取」两个方向排查。

6. 实践要点与限制说明

  • 构建耗时:builder 阶段需要完整编译 Rust 后端(release 模式)并执行 Flutter Linux 构建,且每次 docker-compose build 都会触发 flutter_clean 后的全量重建,因此首次构建耗时长属预期行为;
  • 适用平台:这套文件面向 Linux x86_64(-p production-linux-x86_64build/linux/x64/release/bundle 路径均为 x64),产物运行于具备 X11 显示服务的 Linux 宿主机上;
  • 版本基线:本文所有路径、版本号(Flutter 3.27.4、Rust 1.81、cargo-make 0.37.18 等)均以当前仓库中 Dockerfiledocker-compose.ymlmakefile 任务定义 的实际内容为准;
  • 相关入口:如需查看 AppFlowy 构建系统的完整任务列表,可浏览 frontend/Makefile.toml 聚合的各 scripts/makefile 子配置,Docker 构建正是复用其中 appflowy-linux 这条与本机开发完全一致的构建链路,保证镜像产物与本地 cargo make appflowy-linux 产物等价。
登录后查看全文
热门项目推荐
相关项目推荐