如何使用 Docker Compose 自托管 AFFiNE 工作区
通过 Docker Compose 部署 AFFiNE,详解四个核心容器的作用、Postgres 数据持久化配置、Redis 依赖关系及 2GB 内存下的实际运行表现。本指南涵盖镜像标签锁定、备份策略及常见启动失败排查,助您在 VPS 上稳定运行 Notion 替代方案。
自托管 AFFiNE 的实际情况
自托管 AFFiNE 后,您可以在自己控制的服务器上运行 Notion 风格的工作区。它由 4 个容器组成:应用、一次性迁移任务、Postgres 和 Redis。系统包含实时协作功能,自托管工作区默认最多支持 10 个席位。安装只需要 1 个 compose 文件和 1 个 JSON 配置文件。需要重点规划的是镜像标签、磁盘布局、内存上限,以及前置代理。
AFFiNE 在同一个工作区中同时提供文档编辑器和无限画布。因此,同一页面既可以作为文档阅读,也可以展开为白板。如果您仍在决定要运行哪种系统,请先阅读 自托管 Notion 替代方案对比。本指南假定您已经做出选择,重点介绍如何正确运行 AFFiNE,而不是再次进行比较。
本文内容已根据 AFFiNE 自托管文档以及 2026 年 8 月 8 日发布的版本文件完成核对。当天最新的稳定版本是 0.27.3,于 2026 年 7 月 23 日发布。下文带有更晚日期的内容已于 2026 年 10 月 12 日根据主要来源重新核对;当时最新的稳定版本是 0.27.4,于 2026 年 8 月 18 日发布。
四个容器的实际作用
affine 将服务器和 Web 客户端合并在同一个镜像中运行。它监听 3010 端口。
affine_migration 是一个一次性任务,用于运行 node ./scripts/self-host-predeploy.js、执行数据库迁移,然后退出。应用将 condition: service_completed_successfully 声明为该任务的依赖项,因此迁移以非零状态退出时,affine 根本不会启动。Web 界面无法启动时,应首先查看该任务的日志。
postgres 保存您的文档、用户、工作区和权限。发布的镜像是 pgvector/pgvector:pg16,即内置 pgvector 扩展的标准 Postgres 16。pgvector 为 Postgres 添加 vector 列类型。这种数值形式用于存储嵌入向量,从而按语义搜索文本。
redis 是硬依赖项:服务器和迁移任务都会等待其健康检查通过后再启动。请注意,发布的 compose 文件没有为 Redis 提供卷。Redis 中的任何内容都不会在 docker compose down 后保留。这明确表明 Redis 不保存您的任何数据,也不需要备份。
为什么使用 pgvector 镜像,而不是标准 postgres 镜像
该要求来自 AFFiNE 的 schema,而不是个人偏好。在 v0.27.4 中,schema.prisma 仍声明了 extensions = [pgvector(map: "vector")],但不包含 embedding 列。v0.27.3 中有 4 个模型的列类型为 vector(1024),并带有 embedding,但这些模型在 v0.27.4 中已不存在。请保留提供的 pgvector/pgvector:pg16 镜像:该扩展仍在声明中,而标准 postgres:16 不包含它。
官方 v0.27.4 模板使用 pgvector 镜像;v0.21.0 模板仍使用 postgres:16。在旧版本安装上,编辑镜像行并不能完成整个升级过程。因此,在拉取任何内容前,请先阅读 AFFiNE 自托管文档中的升级页面。
还需要注意该标签。pg16 表示 Postgres 16,Postgres 主版本不能直接递增。将其改为 pg17 并继续使用现有数据目录时,Postgres 会拒绝启动,并在 docker compose logs postgres 中记录类似 The data directory was initialized by PostgreSQL version 16, which is not compatible with this version 17 的行。主版本迁移需要采用 PostgreSQL 17 发布说明中列出的 3 种方式之一:将数据转储并恢复到新的数据目录、pg_upgrade,或使用逻辑复制。对于单机 Compose 安装,转储和恢复通常最简单,因为 pg_upgrade 需要旧版和新版服务器二进制文件同时存在。
自托管 AFFiNE 需要多少 CPU 和 RAM
AFFiNE 的需求页面要求至少 4 个 CPU 核心和 2 GB RAM;当文档超过 10,000 个单词后,内存要求提高到 4 GB。同一页面还说明了内存的用途:同步系统和文档合并。页面给出了一个值得记住的数据:合并包含 10,000 项修改的文档时,内存峰值可能达到 1 GB。
以 2 GB 方案、两人同时编辑为例。平均使用量没有问题。Postgres 和 Node 进程都能保持在限制以内,并留有余量。问题在于峰值。一次大型合并就可能使服务器内存使用量达到约 1 GB。在没有 swap 的 2 GB 服务器上,这个峰值加上 Postgres 及其他常驻进程,可能耗尽内存。此时,内核的内存不足(OOM)终止程序以释放内存。AFFiNE server 是最大的进程,因此很可能成为终止对象。不过,Docker 文档明确指出,任何进程都可能被选中,包括 Docker 本身。
如果被终止的是 AFFiNE server,您的同事不会看到错误。他们只会看到页面重新加载,因为 restart: unless-stopped 会在几秒内重新启动容器。不要凭猜测判断,请确认:
docker inspect affine_server --format '{{.State.OOMKilled}} {{.RestartCount}}'
sudo dmesg -T | grep -i -E 'out of memory|killed process'第一个命令的输出中出现 true,或者第二个命令中出现包含 node 的 Killed process 行,说明内存已耗尽,而不是发现了程序错误。请从两端处理。先添加 swap,让内存峰值在 OOM 终止程序前有缓冲空间:
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
free -h此时,free -h 应报告 2.0Gi 的 swap 总量。swap 不会让 AFFiNE 运行得更快,也不是为此设计的。它为短暂的内存峰值提供了去处,因此峰值更可能只造成短暂变慢,而不是导致容器终止。它是缓冲区,不是保证。另一端是阻止 Postgres 将缓存扩展到应用在合并时需要的空间,这正是 Compose 服务的内存限制 的作用。如果同时完成这两项后仍然发生终止,说明服务器需要 AFFiNE 需求页面针对较大文档所要求的 4 GB 内存。
存储空间更容易预测。以下是 AFFiNE 在同一页面发布的数据:
此图表背后的数据
[
{
"label": "Server install",
"gb": 1.5
},
{
"label": "Postgres per 1,000 docs",
"gb": 0.1
},
{
"label": "Blob store per 1,000 uploads",
"gb": 10
}
]服务器安装占用 1.5 GB。1,000 个、每个约 1,000 个单词的文档只会增加 0.1 GB 的 Postgres 数据,几乎可以忽略。1,000 个上传文件会增加 10 GB,这才是主要因素。这些是公开的规划数据,不是运行中实例的实测结果,因此应将其视为规模参考,而不是保证值。关键在于增长形态:数据库会保持较小,而上传文件决定磁盘用量。
自行编写 Compose 文件,并固定镜像标签
文档中的安装步骤会使用 curl -L -o docker-compose.yml https://github.com/toeverything/AFFiNE/releases/latest/download/docker-compose.yml 下载现成文件。该方法可行。但在依赖它之前,有一点需要了解:截至 2026 年 8 月 8 日,附加在版本 0.27.3 中的文件仍会从 .env 文件读取路径,并使用 ${UPLOAD_LOCATION}、${CONFIG_LOCATION} 和 ${DB_DATA_LOCATION};而文档的参考页面已经展示了更新后的布局,将存储和 PostgreSQL 数据放在 ./data 下,将配置保留在 ./config 下,并且完全不需要 .env。版本 0.27.4 将附加文件改为这一布局。截至 2026 年 10 月 12 日,两者已经一致。如果您从旧文件安装,路径仍来自 .env。AFFiNE 的升级页面警告说,如果替换 Compose 文件时没有保留这些设置,Postgres 可能会使用空数据目录启动。自行编写文件可以完全避免这个问题,而且无论如何都需要编辑文件来固定镜像版本并设置数据库密码。
mkdir -p ~/affine/config ~/affine/data
cd ~/affine
printf 'DB_PASSWORD=%s\n' "$(openssl rand -hex 24)" > .env
chmod 600 .envCompose 会自动从项目目录读取 .env,并替您替换 ${DB_PASSWORD},因此密码不会出现在您可能粘贴到支持帖中的文件里。无论运行哪套服务,都建议保持这一习惯。相关原因请参阅不要将机密信息写入 Compose 文件。
现在编写 ~/affine/docker-compose.yml:
name: affine
services:
affine:
image: ghcr.io/toeverything/affine:stable
container_name: affine_server
ports:
- '127.0.0.1:3010:3010'
depends_on:
redis:
condition: service_healthy
postgres:
condition: service_healthy
affine_migration:
condition: service_completed_successfully
volumes:
- ./data/storage:/root/.affine/storage
- ./config:/root/.affine/config
environment:
- REDIS_SERVER_HOST=redis
- DATABASE_URL=postgresql://affine:${DB_PASSWORD}@postgres:5432/affine
- AFFINE_INDEXER_ENABLED=false
restart: unless-stopped
affine_migration:
image: ghcr.io/toeverything/affine:stable
container_name: affine_migration_job
command: ['sh', '-c', 'node ./scripts/self-host-predeploy.js']
volumes:
- ./data/storage:/root/.affine/storage
- ./config:/root/.affine/config
environment:
- REDIS_SERVER_HOST=redis
- DATABASE_URL=postgresql://affine:${DB_PASSWORD}@postgres:5432/affine
- AFFINE_INDEXER_ENABLED=false
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
redis:
image: redis:8-alpine
container_name: affine_redis
healthcheck:
test: ['CMD', 'redis-cli', '--raw', 'incr', 'ping']
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
postgres:
image: pgvector/pgvector:pg16
container_name: affine_postgres
volumes:
- ./data/postgres:/var/lib/postgresql/data
environment:
POSTGRES_USER: affine
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: affine
POSTGRES_INITDB_ARGS: '--data-checksums'
healthcheck:
test: ['CMD', 'pg_isready', '-U', 'affine', '-d', 'affine']
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped与上游发布的文件相比,这里有 4 处差异,每处都有明确原因。
127.0.0.1:3010:3010仅在 loopback 地址上发布端口。28.0.0 之前的 Docker 版本允许同一 L2 网段中的主机访问发布到 localhost 的端口;启用直接路由后,即使发布到 loopback,也可能暴露容器端口。请使用 Docker 28.0.0 或更高版本,并在安装期间保持直接路由禁用。上游的'3010:3010'会绑定所有网络接口,而在大多数 VPS 镜像中,这也包括公网接口。- 删除
POSTGRES_HOST_AUTH_METHOD: trust,改为设置密码。信任认证允许任何连接到该数据库的客户端以affine用户身份登录,且无需密码。该方式仅限于私有 Compose 网络。在您向该网络再连接一个容器,或为排查问题发布 5432 端口之前,这通常没有问题。 redis:8-alpine替代了未固定版本的redis,后者会解析为latest。截至 2026 年 8 月,该标签对应 Redis 8。固定版本可以保留经过测试的主版本,并避免未来的 Redis 9 在无关的docker compose pull期间被拉取。pgvector/pgvector:pg16保持上游的原样,原因如上所述。
POSTGRES_PASSWORD 仅在 Postgres 首次创建数据目录时读取。对于已经存在的实例,请使用 docker compose exec postgres psql -U affine -c "ALTER USER affine WITH PASSWORD 'yourpassword'" 设置密码,然后相应更新 DATABASE_URL。
配置文件位于 config/config.json
AFFiNE 从 config/config.json 读取设置。该目录就是您挂载到 /root/.affine/config 的目录。系统不会自动创建此文件,因此请在首次启动前手动创建。使用编辑器打开 ~/affine/config/config.json,填入以下内容,并将示例域名替换为您自己的域名:
{
"$schema": "https://github.com/toeverything/affine/releases/latest/download/config.schema.json",
"server": {
"name": "Team workspace",
"externalUrl": "https://affine.example.com"
},
"copilot": {
"enabled": false,
"byok": {
"enabled": false
}
}
}server.externalUrl 必须是用户实际在浏览器中打开的地址。AFFiNE 会根据该值生成共享链接和工作区邀请。如果保留为 http://localhost:3010,您发送的邀请会将接收者指向他们自己的计算机,并在那里失效。请在首次启动前将其设置为公开的 HTTPS 地址,确保配置文件和管理面板中的地址一致。之后在管理面板中修改的设置会保存到数据库,不会写回此文件。
copilot 控制 AI 功能。copilot.byok.enabled 是自带密钥开关,允许工作区所有者在工作区设置中粘贴自己的模型提供商密钥。自行托管 AFFiNE 不包含 AI 订阅。如果您不需要 AI 功能,请将 false 保持为关闭状态。
启动服务栈:
docker compose up -d
docker compose ps --alldocker compose ps --all 应将 affine_postgres 和 affine_redis 列为 healthy,将 affine_server 列为 running,并将 affine_migration_job 的状态显示为 exited (0)。--all 很重要:没有它,docker compose ps 只会显示正在运行的容器,因此已正常完成的迁移任务根本不会显示。迁移任务出现其他退出代码时,应重点排查该问题;其日志会指出停止执行的步骤:
docker compose logs affine_migration固定镜像,避免之后遗忘
stable 是一个会变化的标签。AFFiNE 的发布流程会为每个稳定版本设置多个标签,其中两个与此处有关:stable 会在每次发布时重新指向新版本,stable- 后接 git 短哈希,且不会变化。如果一直使用 stable,那么六个月后执行 docker compose pull 会获取不同的镜像,并在你未选择的时间对数据库执行迁移。请固定为已测试的确切镜像:
docker compose pull
docker image inspect ghcr.io/toeverything/affine:stable --format '{{index .RepoDigests 0}}'该命令会输出类似 ghcr.io/toeverything/affine@sha256: 后接一长串哈希值的内容。将完整字符串粘贴到 affine 和 affine_migration 的 image: 行中。这两个值必须始终一致,因为它们表示同一个镜像,只是承担不同的角色;如果不一致,就可能将数据库迁移到一种架构,同时使用另一种架构提供服务。这样,升级就变成有意执行的编辑操作,而不是意外事件:修改摘要,备份,docker compose pull,docker compose up -d。
在其他人之前创建管理员账户
在全新实例上打开 /admin 时,AFFiNE 会将您转到账户创建页面,因为服务器尚未设置管理员。此流程不需要邀请代码或设置令牌。第一个完成注册的人将成为您服务器的管理员,因此完成注册前必须保持端口关闭。
因此,上面的 compose 文件绑定到 127.0.0.1。通过本机上的 SSH 隧道访问该地址:
ssh -L 3010:127.0.0.1:3010 you@your-server-ip保持隧道运行,然后在本机浏览器中打开 http://127.0.0.1:3010/admin。完成注册并登录后,关闭隧道。现在才可以安全地为该实例配置公网域名。其他自托管应用也存在相同的竞争条件。如果首次登录会创建与主机名绑定的通行密钥,风险会更高。因此,在 自托管 openGym 时,必须在创建第一个账户前确定 TLS 配置和最终域名。
AFFiNE 在哪里保存数据
3 个路径保存了全部数据,并且都位于您创建的目录中。
./data/postgres是 Postgres 数据目录,其中包含文档、用户、工作区、权限,以及在管理面板中保存的服务器设置。./data/storage在容器中挂载到/root/.affine/storage,用于保存所有上传的文件。./config挂载到/root/.affine/config,用于保存config.json以及迁移任务生成的private.key。
上游项目在这里使用绑定挂载,而不是命名卷,这是经过刻意选择的方案:您可以使用普通命令打包并复制这些路径,无需询问 Docker 将它们存放在哪里。代价是,主机上的文件所有权需要由您负责。这正是 绑定挂载和命名卷 中介绍的权衡。
如何备份 AFFiNE
需要备份两类数据,且备份方式不同。数据库是运行中的服务,因此在服务运行时直接复制其文件会得到损坏的副本。请改为导出数据库:
mkdir -p ~/affine/backup
cd ~/affine
docker compose exec -T postgres pg_dump --format c --username affine affine \
> backup/affine-$(date +%F).dump
ls -lh backup/导出操作在容器内通过本地套接字执行,因此不会提示输入密码。检查 ls 输出中的文件大小。只有几百字节的文件表示导出失败,但 shell 仍然创建了文件;这通常会在 6 个月后才被发现。-T 也很重要:如果没有它,Compose 可能会分配终端,从而损坏二进制数据流。
上传的文件就是普通文件,因此使用 tar 归档:
tar czf backup/storage-$(date +%F).tgz -C data storage
tar czf backup/config-$(date +%F).tgz config docker-compose.yml .env将整个 config 目录、Compose 文件和 .env 都保存在备份中,因为数据库导出文件不包含它们。迁移任务还会创建 config/private.key,因此只复制 config.json 会遗漏该文件。管理面板中保存的设置存储在数据库内,因此上面的数据库导出已经包含这些设置。截至 2026 年 10 月 12 日,AFFiNE 的备份页面还说明,可以从管理面板导出已修改的服务器设置。将这 3 个文件全部复制到服务器之外。与受保护数据位于同一磁盘上的备份不是真正的备份。数据库导出文件加上传文件目录 tar 归档的组合方式,也适用于你运行的其他有状态容器;当你 将 Chatwoot 自托管为支持服务台 时,同样可以用这种方式保护会话历史和附件。
恢复数据,以及发布步骤中的一个陷阱
请在需要恢复数据之前阅读官方恢复步骤,并仔细核对。官方页面在 2026 年 8 月发布的版本中,将名为 affine.backup 的文件复制到容器中,然后从 ./pg.backup 恢复;这两个名称并不相同。此外,该版本还删除了 ./postgres 目录,而当前 compose 文件将数据保存在 ./data/postgres 中。截至 2026 年 10 月 12 日,该页面已统一使用 affine.backup,并说明在确认恢复成功前应保留旧的 Postgres 目录。因此,上游页面已经修复了这个陷阱,但在此之前保存的步骤副本仍然包含该问题。无论使用哪个版本,都应按照实际使用的路径操作,而不是照抄代码片段中的路径。下面是按照本指南目录结构执行的步骤:
cd ~/affine
docker compose down
sudo mv data/postgres data/postgres.old
docker compose up -d postgres
docker compose cp backup/affine-2026-08-08.dump postgres:/tmp/affine.dump
docker compose exec postgres pg_restore --format c --username affine \
--dbname affine --verbose /tmp/affine.dump
docker compose up -d注意使用 mv,而不是 rm。如果没有保留副本就直接覆盖数据库,一条错误命令可能导致全部数据丢失;将旧目录移到其他位置不会产生任何成本。还要使用 tar xzf backup/storage-2026-08-08.tgz -C data 恢复上传文件,否则所有文档中的附件都会显示为损坏。然后登录并打开一个包含图像的文档。这才是测试。未在浏览器中打开验证过的恢复结果只是文件,不是备份。
将 AFFiNE 放在现有代理之后
AFFiNE 使用 WebSocket,且这是必需的。文档对此有明确说明:WebSocket 是 AFFiNE 同步和协作系统的基础。如果代理不升级这些连接,工作区仍会正常打开,但编辑内容会静默停止同步。页面可以加载,登录也能成功,但在一个浏览器中进行的编辑不会传到另一个浏览器。在浏览器开发者工具中打开 Network 选项卡,并筛选 WS。反复打开和关闭的连接,通常表示代理没有转发升级请求。
如果您已经为其他容器运行 Traefik,可以将 AFFiNE 作为普通服务接入。删除 ports: 服务中的 affine 块,然后添加:
networks:
- default
- proxy
labels:
- 'traefik.enable=true'
- 'traefik.docker.network=proxy'
- 'traefik.http.routers.affine.rule=Host(`affine.example.com`)'
- 'traefik.http.routers.affine.entrypoints=websecure'
- 'traefik.http.routers.affine.tls.certresolver=letsencrypt'
- 'traefik.http.services.affine.loadbalancer.server.port=3010'在文件底部、与 services: 同级的位置添加:
networks:
proxy:
external: true证书解析器名称必须与 Traefik 配置中定义的名称一致,loadbalancer.server.port 是容器端口 3010,不能填写主机端口。Traefik 无需额外配置即可代理 WebSocket 连接,因此不需要再添加其他内容。如果其余服务已经通过 Authentik 实现单点登录,可以在此路由器上添加 forward auth 中间件,以限制浏览器访问 AFFiNE。但在测试桌面应用之前不要启用它,因为桌面应用没有浏览器会话,启用后会直接无法同步。如何让多个应用通过同一个 Traefik 实例运行,请参阅在多个应用前使用单个 Traefik。
使用 nginx 时,必须显式请求升级:
location / {
proxy_pass http://127.0.0.1:3010;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
client_max_body_size 100m;
}nginx 中的 client_max_body_size 默认值为 1 MB。如果没有这一行,所有大于小型照片的上传都会失败并返回 413 状态码,而且 AFFiNE 日志中不会出现任何内容,因为请求根本没有到达 AFFiNE。Caddy 只需添加一行 reverse_proxy http://127.0.0.1:3010,即可自行处理证书和 WebSocket 升级。
自托管版本不包含的功能
在将团队迁移过来之前,请先如实评估这一点。
实时协作功能可用。这也是所有容量建议都围绕的功能,因为 AFFiNE 自己的文档将内存使用归因于同步系统和文档合并。离线编辑是许多人选择本地优先工具的原因。桌面应用可以将您的自托管服务器添加到工作区列表,并使用该服务器登录。请在确定方案前测试团队依赖的确切离线行为:关闭网络后在桌面应用中编辑,重新连接,然后在第二台设备上检查结果。功能列表不能作为证据,这一列表也一样。
服务器端全文搜索在随附的 compose 文件中处于关闭状态,其中 AFFINE_INDEXER_ENABLED=false 在服务器和迁移任务中均已设置。启用该功能需要添加一个 Manticore Search 容器。这样会增加第 5 个服务,并占用更多内存。在 2 GB 的服务器上,这项改动会让内存使用超出限制。客户端内的搜索仍可用于当前打开的工作区。
在邀请其他人加入之前,需要了解以下两项限制。自托管工作区最多可分配 10 个席位。超过该数量后,需要从 AFFiNE 获取 Team 许可证。文档将自托管实例的无限 blob 存储和无限 blob 大小描述为计划功能,但截至 2026 年 10 月尚未完全实现。对于家庭用户或小型团队,这两项限制都无关紧要。如果您计划迁移 40 人,则两项限制都会产生影响。
升级
先阅读发行说明,尤其是从 0.26 升级到 0.27 这类次版本升级,因为其中可能包含不兼容变更。执行任何操作前,先备份数据库和存储目录,因为迁移任务会在下次启动时修改数据库架构,且无法撤销。然后修改固定的摘要,运行 docker compose pull,再运行 docker compose up -d,并监控 docker compose logs -f affine_migration,直到它正常退出。之后,docker image prune 会清理旧层。
对于使用非常旧版本的用户,还需注意一项历史变更:从 0.23.0 开始,镜像名称从 affine-graphql 更改为 affine。因此,早于该版本的 compose 文件需要先重写其中的镜像配置行,之后拉取操作才能找到镜像。从 0.27.4 开始,官方 compose 模板还调整了 Postgres、上传和配置路径,并更改了默认数据库连接。因此,请将新文件与现有文件进行比较,不要直接覆盖正在运行的安装。
FAQ
AFFiNE 容器为什么始终无法启动?
affine 服务在 affine_migration 作业上声明了 condition: service_completed_successfully,因此只要迁移以非 0 状态退出,服务器就不会启动,Web 界面也完全不会出现。运行 docker compose logs affine_migration,查看在哪个步骤停止。检查手动编辑的 Compose 文件是否仍使用 pgvector/pgvector:pg16:标准 Postgres 不包含 pgvector 扩展,而 AFFiNE 的架构仍声明需要该扩展,尽管 v0.27.4 已不再定义 v0.27.3 中存在的 4 个嵌入模型。
自托管 AFFiNE 需要多少 RAM?
AFFiNE 的要求页面建议至少使用 4 个 CPU 核心和 2 GB RAM;当文档超过 10,000 个词时,RAM 要求增加到 4 GB。页面还指出,合并包含 10,000 项修改的文档时,内存峰值可能达到 1 GB。在 2 GB 服务器上,导致问题的是这个峰值,而不是空闲负载:内核的 OOM killer 会选择一个进程终止,最可能终止作为最大进程的 AFFiNE 服务器,而 restart: unless-stopped 会再次启动它,因此用户看到的是页面重新加载,而不是错误。使用 docker inspect affine_server --format '{{.State.OOMKilled}}' 和 sudo dmesg -T | grep -i 'out of memory' 确认这一点,然后添加 2 GB 交换文件,以缓冲短时峰值。如果进程仍被终止,请将 RAM 增加到 4 GB。
AFFiNE 将数据存储在哪里?需要备份什么?
Compose 目录下的 3 个路径包含全部数据:./data/postgres 用于数据库,./data/storage 用于上传的文件,./config 用于 config.json 和生成的 private.key。使用 docker compose exec -T postgres pg_dump --format c --username affine affine > affine.dump 备份数据库,不要直接复制文件,因为运行中的 Postgres 无法安全复制。该转储文件也包含管理面板中保存的设置,因为 AFFiNE 将这些设置存储在数据库中。使用 tar 打包 ./data/storage 以备份上传文件,并保留整个 config 目录、Compose 文件和 .env,因为这些文件不属于数据库。
自托管 AFFiNE 支持实时协作吗?
支持,无需启用任何选项。唯一的要求是正确配置反向代理,因为同步通过 WebSocket 连接运行。对于 nginx,这意味着配置 proxy_http_version 1.1 以及 Upgrade 和 Connection: upgrade 标头;Traefik 和 Caddy 无需额外配置即可转发这些连接。如果代理未将连接升级为 WebSocket,工作区仍会正常加载并登录,但在一个浏览器中进行的编辑不会出现在另一个浏览器中。
可以使用标准 Postgres 镜像运行 AFFiNE 吗?
请使用 pgvector/pgvector:pg16,它是内置 pgvector 扩展的 Postgres 16。AFFiNE v0.27.4 仍在 schema.prisma 中声明 extensions = [pgvector(map: "vector")],但不再定义 v0.27.3 中使用 vector(1024) 嵌入列的 4 个模型。标准 Postgres 不包含所声明的扩展。如果改为让 AFFiNE 连接外部 Postgres 服务器,请在该服务器上安装 pgvector,并在运行迁移前于目标数据库中创建该扩展。