SSD Nodes Learn Hosting plans →
指南 Matt Connor作者: Matt Connor · 更新于 2026-08-27

自行托管 AFFiNE:Docker Compose 部署指南

使用 Docker Compose 在一台 VPS 上运行 AFFiNE 0.27.3,了解 4 个容器、固定镜像标签、数据位置与备份方式,以及 2 GB 内存的实际承载能力。

自行托管 AFFiNE 的结果

自行托管 AFFiNE 后,您可以在自己控制的服务器上运行类似 Notion 的工作区。它由 4 个容器组成:应用、一次性迁移任务、Postgres 和 Redis。系统包含实时协作功能。自行托管的工作区默认最多支持 10 个席位。安装只需要一个 compose 文件和一个 JSON 配置文件。需要重点考虑的是镜像标签、磁盘布局、内存上限,以及前置代理的配置。

AFFiNE 在同一个工作区中同时提供文档编辑器和无限画布。因此,同一页面既可以作为文档阅读,也可以展开为白板。如果您还在决定要运行哪种服务,请先阅读 自行托管的 Notion 替代方案对比。本指南假定您已经做出选择,内容重点是正确运行 AFFiNE,而不是再次进行比较。

本文内容已根据 AFFiNE 自托管文档和已发布的版本文件进行核对,核对日期为 8 August 2026。当日最新的稳定版本是 0.27.3,发布日期为 23 July 2026。

四个容器的实际作用

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.prisma 中,数据源声明了 extensions = [pgvector(map: "vector")],并且有 4 个表包含类型为 vector(1024)embedding 列。无论是否启用 AI 功能,迁移任务都会创建这些表。因此,迁移完成前,数据库中必须已经存在该扩展。换成 postgres:16 后,扩展就会消失,迁移无法创建这些列,服务器会一直等待一个已经失败的任务。

AFFiNE 从 0.21 版本开始改用 pgvector 镜像。如果安装版本早于该版本,仅修改镜像配置行并不足以完成升级。拉取任何内容前,请先阅读 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 的错误行。升级主版本需要先执行转储,再将数据恢复到新的数据目录。

自托管 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 服务器上,内核的内存不足(OOM)终止程序会通过终止占用内存最多的进程来满足该请求,而这个进程就是 AFFiNE 服务器。

您的同事不会看到错误。他们只会看到页面重新加载,因为 restart: unless-stopped 会在几秒内重新启动容器。不要凭猜测判断,直接确认:

docker inspect affine_server --format '{{.State.OOMKilled}} {{.RestartCount}}'
sudo dmesg -T | grep -i -E 'out of memory|killed process'

第一条命令输出中的 true,或第二条命令输出中包含 nodeKilled process 行,表示内存耗尽,而不是发现了程序错误。请从两端修复。先添加 swap,让内存峰值变慢,而不是直接导致服务终止:

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 在同一页面公布的数据:

ChartPublished AFFiNE storage figures, August 2026
The data behind this chart
[
  {
    "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 日,release 0.27.3 附带的文件仍会通过 ${UPLOAD_LOCATION}${CONFIG_LOCATION}${DB_DATA_LOCATION}.env 文件读取路径,而文档的参考页面展示了较新的布局,将所有内容放在 ./data 下,完全不需要 .env。这两种布局都是真实存在的。自行编写文件可以消除这个疑问,而且无论如何都需要编辑文件,以固定镜像版本并设置数据库密码。

mkdir -p ~/affine/config ~/affine/data
cd ~/affine
printf 'DB_PASSWORD=%s\n' "$(openssl rand -hex 24)" > .env
chmod 600 .env

Compose 会自动从项目目录读取 .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 地址上发布端口,因此在您决定访问方式之前,服务器外部无法访问 AFFiNE。上游的 '3010:3010' 会绑定所有接口,在大多数 VPS 镜像中也包括公网接口。
  • 删除了 POSTGRES_HOST_AUTH_METHOD: trust,改为设置密码。Trust 身份验证允许任何连接到该数据库的客户端以 affine 用户身份访问,且无需密码。虽然连接仅限于私有 Compose 网络,但如果以后将另一个容器接入该网络,或在调试时发布 5432 端口,就会产生风险。
  • redis:8-alpine 替换了未固定版本的 redis,后者会解析到 latest。截至 2026 年 8 月,这是 Redis 8,因此固定版本可以保留已测试的主版本,并避免未来在无关的 docker compose pull 期间升级到 Redis 9。
  • 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

docker compose ps 应将 affine_postgresaffine_redis 列为 healthy,将 affine_server 列为 running,并将 affine_migration_job 的状态显示为 exited (0)。迁移任务出现其他退出代码时,应重点排查;其日志会指出停止的步骤:

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: 后接一长串哈希值的内容。将完整字符串粘贴到 affineaffine_migrationimage: 行中。这两个值必须始终一致,因为它们表示同一个镜像,只是承担不同的角色;如果不一致,就可能将数据库迁移到一种架构,同时使用另一种架构提供服务。这样,升级就变成有意执行的编辑操作,而不是意外事件:修改摘要,备份,docker compose pulldocker 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。完成注册并登录后,关闭隧道。只有此时,才可以安全地为实例配置公网域名。其他自托管应用也存在相同的竞争问题。如果首次登录会创建与主机名绑定的 passkey,风险会更高。这也是您在 自托管 openGym 时,必须在创建第一个账户前确定 TLS 配置和最终域名的原因。

AFFiNE 保存数据的位置

有 3 个路径保存全部数据,并且都位于您创建的目录中。

  • ./data/postgres 是 Postgres 数据目录,包含文档、用户、工作区和权限。
  • ./data/storage 在容器中挂载到 /root/.affine/storage,包含所有上传的文件。
  • ./config 挂载到 /root/.affine/config,包含 config.json

上游配置在这里使用 bind mount,而不是命名卷,这是经过考虑的选择:您可以使用普通命令对这些路径执行 tar 打包和复制,无需询问 Docker 将它们存放在哪里。代价是,主机上的文件所有权现在由您负责。这正是 bind mount 和命名卷 中介绍的权衡。

如何备份 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/

转储在容器内通过本地 socket 运行,因此不会提示输入密码。检查 ls 输出中的文件大小。几百字节的文件通常表示转储失败,但 shell 仍然创建了文件;这种问题往往会在六个月后才被发现。-T 同样很重要:没有它,Compose 可能会分配终端并损坏二进制流。

上传的文件本身就是普通文件,因此使用 tar 打包:

tar czf backup/storage-$(date +%F).tgz -C data storage
cp config/config.json backup/config-$(date +%F).json

手动将 config.json 保留在备份中。截至 2026 年 8 月,AFFiNE 文档仍将管理面板中的配置导出列为未实现,因此磁盘上的文件是设置的唯一副本。将这 3 个文件全部复制到服务器之外。与受保护数据位于同一磁盘上的备份不是真正的备份。数据库转储加上 uploads 目录 tar 归档的拆分方式,应作为其他有状态容器的通用备份模式;当您 自行托管 Chatwoot 作为支持工单系统 时,也应使用同样的方式保护会话历史和附件。

恢复数据,以及已发布步骤中的一个陷阱

在需要恢复数据之前,先阅读官方恢复步骤,并仔细核对。根据 2026 年 8 月发布的步骤,命令会将名为 affine.backup 的文件复制到容器中,然后从 ./pg.backup 恢复。这两个名称并不相同。命令还会删除 ./postgres 目录,而当前 compose 文件将数据保存在 ./data/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 年 8 月仍是如此。这两项限制对家庭用户或小型团队都没有影响。如果您计划迁移 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 文件需要先重写其中的镜像配置行,否则 pull 将找不到镜像。

FAQ

AFFiNE 容器为什么始终无法启动?

affine 服务在 affine_migration 作业上声明了 condition: service_completed_successfully,因此只要迁移以非 0 状态退出,服务器就不会启动,Web 界面也完全不会出现。运行 docker compose logs affine_migration,查看在哪个步骤停止。手动编辑 compose 文件时,最常见的原因是使用了标准的 postgres 镜像,而不是 pgvector/pgvector:pg16,因为 AFFiNE schema 声明了 pgvector 扩展,并会创建包含 vector(1024) 列的表,而普通 Postgres 无法创建这些列。

自托管 AFFiNE 需要多少 RAM?

AFFiNE 的要求页面指出,至少需要 4 个 CPU 核心和 2 GB RAM;当文档超过 10,000 个词时,RAM 要求增加到 4 GB。页面还说明,合并包含 10,000 项修改的文档时,内存峰值可能达到 1 GB。在 2 GB 服务器上,导致进程终止的是这个峰值,而不是空闲负载:内核的 out-of-memory killer 会停止 AFFiNE 进程,随后 restart: unless-stopped 会再次启动它,因此用户看到的是页面重新加载,而不是错误。使用 docker inspect affine_server --format '{{.State.OOMKilled}}'sudo dmesg -T | grep -i 'out of memory' 确认这一点,然后添加 2 GB swap 文件,使突发负载变慢,而不是直接导致服务终止。

AFFiNE 将数据存储在哪里?我需要备份什么?

compose 目录下的 3 个路径包含全部数据:./data/postgres 用于数据库,./data/storage 用于上传的文件,./config 用于 config.json。使用 docker compose exec -T postgres pg_dump --format c --username affine affine > affine.dump 备份数据库,不要直接复制文件,因为运行中的 Postgres 无法安全复制。使用 tar 归档 ./data/storage 中的上传文件,并手动保留一份 config.json,因为截至 August 2026,管理面板中的配置导出仍标记为尚未实现。

自托管 AFFiNE 支持实时协作吗?

支持,而且无需启用任何额外功能。唯一的要求是正确配置反向代理,因为同步通过 WebSocket 连接运行。对于 nginx,需要配置 proxy_http_version 1.1 以及 UpgradeConnection: upgrade 标头;Traefik 和 Caddy 无需额外配置即可转发这些连接。如果代理未将连接升级,表现为工作区可以正常加载并登录,但在一个浏览器中进行的编辑不会出现在另一个浏览器中。

AFFiNE 可以使用标准 Postgres 镜像运行吗?

不可以。AFFiNE 的 schema.prisma 声明了 extensions = [pgvector(map: "vector")],并定义了 4 个包含 embedding 列的表,该列类型为 vector(1024)。即使关闭 AI 功能,迁移作业仍会创建这些表。请使用 pgvector/pgvector:pg16,这是一个内置该扩展的 Postgres 16。如果改为让 AFFiNE 连接外部 Postgres 服务器,请在该服务器上安装 pgvector,并在目标数据库中创建该扩展,然后再运行迁移。

#affine#notion-alternative#docker-compose#自托管#knowledge-base