ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

AFFiNE 自托管实战:用 Docker 部署三合一工作台,彻底替代 Notion

AFFiNE 自托管实战:用 Docker 部署三合一工作台,彻底替代 Notion 最近把主力笔记从 Notion 迁到了 AFFiNE起因很简单受够了在 Notion、Miro、Airtable 之间来回切来切去。写需求文档要开 Notion画流程图要开 Miro整理任务状态又要去翻数据库看板三个工具的账号、权限、数据格式各不相同光是复制粘贴就浪费了大量时间。AFFiNE 的定位恰好就是把这三种能力揉进同一个工作区——文档、白板、数据库不再割裂而是在同一个画布上无缝切换。加上它本身开源、本地优先、支持 Docker 自托管我果断用 Docker 部署了一整套实测了几周之后把 Notion 卸载了。这篇文章不是给 AFFiNE 写软文纯粹是从一个自托管用户的角度把它的核心设计、Docker 部署步骤、三大场景的实际体验、还有踩过的坑一次说清楚。如果你也在犹豫要不要换掉 Notion或者正在找一款能自托管的本地优先协作工具这篇文章应该能帮你省下不少试错时间。1. 三合一到底怎么理解AFFiNE 的核心设计思路1.1 它和 Notion、Miro、Airtable 的关系AFFiNE 官方给自己的定位是“Everything app”听起来很虚但拆开看其实很实在。它最核心的是把三种工作形态统一到一个底层模型里文档Doc、画布Edgeless、数据库Database。在 Notion 里这三种能力是割裂的模块你得在不同页面之间跳转在 AFFiNE 里它们是同一个页面的不同视图随时可以切换。举个例子我在 AFFiNE 里建了一个页面写一篇产品需求文档。默认状态下它是普通文档模式敲字、加标题、插图片和 Notion 体验差不多。但一旦我把页面切换到 Edgeless 模式整篇文档的块就全部变成了画布上的实体卡片。我可以把需求描述卡片拉到左边把流程图相关卡片拉到右边中间用连线器画箭头旁边再摆一个数据库看板展示任务状态。文档里的文字和画布上的图形是同一个东西的不同呈现不是复制粘贴出来的两份内容。这种设计最大的价值不是“功能多”而是“上下文不丢”。以前在 Notion 里写文档想配个图要切到 Figma 截图再贴回来想更新任务状态又要切到另一个数据库页面来回切换的成本很高。在 AFFiNE 里所有东西在同一个空间里白板上画完的图可以直接转成文档里的内容数据库视图也可以嵌入到任意位置作为实时数据源。这不只是省了几次点击而是改变了思维方式——你不再需要先想清楚“这个内容该放进哪个工具”而是随手写在当前上下文里后续再整理成结构化数据。1.2 底层架构基于 Block Suite 的块模型AFFiNE 的底层框架叫 Block Suite这是一套开源的块编辑器基础设施。理解这套东西就能理解为什么 AFFiNE 能同时驾驭文档、白板、数据库三种形态。块模型Block Model是所有现代块编辑器的核心Notion 用的也是类似思路。每一段文字、每一个标题、每一张图片本质上都是一个块Block。块可以嵌套、拖动、转换类型。AFFiNE 的突破在于它把块的定义从“文本块”扩展到了“空间块”。同样一个块在文档模式下渲染成一行文字在 Edgeless 模式下渲染成一张带边框的卡片在数据库模式下渲染成一行记录。这种设计带来的直接影响是数据一致性。你在文档里写了一个标题切到画布模式这个标题变成了画布上的一个节点你再把这个节点拖进某个数据库看板它会变成一条记录。整个过程没有复制粘贴底层还是同一个块对象只是在不同视图下的投影不同。这一点是 AFFiNE 和 Notion 最大的区别Notion 的数据库和页面文档本质上是两种数据模型AFFiNE 则把它们统一了。从部署角度看块模型还有一个好处数据格式简单、可迁移性强。AFFiNE 的数据存储本质上是块树的序列化导出 Markdown、JSON 都不难不存在 Notion 那种导出格式混乱的“数据锁定”问题。这也是我敢放 Doker 自托管的一个重要原因数据在本地格式开放随时可以迁移出去。1.3 为什么选择 Docker 部署而不是直接用官方云服务AFFiNE 官方提供了云服务和桌面客户端为什么我还要折腾 Docker三个原因数据主权、版本可控、成本。数据主权是首要因素。我个人的笔记、项目文档、内部数据库看板虽然谈不上国家机密但也不想让它们平白无故躺在别人的服务器上。AFFiNE 是本地优先local-first设计数据默认存在本地自托管之后所有数据都在我自己这台机器上心里踏实。版本可控也很重要。官方云服务是托管版本版本更新和旧版本退役都由对方决定。自托管则可以锁定版本我先在测试环境验证新版本没问题再决定要不要升级。对于依赖笔记工具干活的人来说稳定性比新功能重要得多。成本更直接。AFFiNE 免费版已经覆盖了绝大多数功能但如果你想要更大的存储空间、更多的团队成员、更长的历史记录官方订阅费用不算低。自托管除了自己那台服务器/迷你主机的电费没有额外成本。当然Docker 部署也有学习门槛。你需要懂基本的 Docker 用法、理解端口映射、会处理数据卷。但这也是这篇文章想解决的事我把完整过程写出来你可以直接照着操作。2. 部署前的功课理解 AFFiNE 的架构与数据存储2.1 两种部署形态单容器快速试用 vs Docker Compose 完整版AFFiNE 官方提供了两种 Docker 部署方式这里先讲清楚区别免得你一上来就选错方案。第一种是最新的all-in-one 单容器镜像。官方把前端静态资源、后端 API、同步 Worker、数据库全打进了同一个镜像里默认用 SQLite 作为数据存储。你只需要一条docker run命令就能拉起来数据落在本地 SQLite 文件里。这种形态适合个人试用、轻度使用不需要额外装数据库和缓存组件很适合快速验证 AFFiNE 是否合口味。第二种是Docker Compose 全家桶。前端、后端、同步 Worker、PostgreSQL、Redis 各自独立成容器通过 Compose 编排起来。数据库用 PostgreSQL缓存和 WebSocket 同步用 Redis。这种形态适合团队使用、生产环境、需要多人实时协作的场景。AFFiNE 的同步机制依赖 Redis 做消息队列和状态缓存多人同时编辑时的实时性主要靠它。如果只是一个人用SQLite 足够但如果有几个同事一起在线编辑强烈建议上 PostgreSQL Redis。我个人的建议是第一轮体验用单容器跑通了、觉得值得长期用再迁到 Compose 的完整形态。不要一上来就全家桶因为 PostgreSQL 和 Redis 本身就引入了一定运维成本你还得操心这两个容器的备份和监控没必要在初始阶段给自己加戏。2.2 环境准备与资源评估先摸一下硬件底子。AFFiNE 是 Node.js TypeScript 技术栈对资源的要求并不算苛刻。单容器模式最核心的容器跑起来需要 1GB 左右的内存加上系统本身整机 2GB 内存可以流畅单用户使用。如果用全家桶PostgreSQL 和 Redis 各自占内存建议至少 4GB 内存起步。磁盘方面AFFiNE 本体镜像约 500MB数据目录最初只有几十 MB但如果你塞了大量图片、文件附件这个目录会持续膨胀。建议留出 20GB 以上可扩展空间日常注意监控磁盘用量。操作系统推荐 Debian/Ubuntu 系的 Linux 服务器或者任何能装 Docker 的旧电脑/迷你主机。Windows 上用 Docker Desktop 也能跑但 Docker Desktop 对虚拟化功能有依赖容易遇到“Virtualization support not detected”这类问题整体体验不如 Linux 顺手。我自己用的是一台 N100 迷你主机装了 Ubuntu 22.04跑这个完全没压力。Docker 环境准备好之后建议先验证一下能否拉取镜像。国内网络环境拉ghcr.io的镜像可能会慢通常有几个解决办法配一个镜像加速器或者改用docker.io上同步的镜像第三方同步源需要自己甄别可靠性也可以直接从有条件的机器上docker save导出再导入。这些属于 Docker 基础操作这里不展开但确实值得提前准备免得卡在第一步。2.3 数据存储逻辑为什么说 AFFiNE 是“本地优先”AFFiNE 的数据存储逻辑值得重点说因为直接决定你的备份策略。在单容器模式下所有数据都写进容器内的一个数据目录运行时通过 volume 挂载到宿主机。常见路径是/root/.affine里面会有一个 SQLite 数据库文件以及上传的图片、附件资源。也就是说只要这个目录在整个工作区就能完整恢复。备份策略非常简单定时把整个目录复制到别处即可。在全家桶模式下业务数据落在 PostgreSQL 里图片和附件落在数据卷里Redis 只做缓存和同步状态本身不需要备份。备份思路变成PostgreSQL 定时pg_dump再加上附件目录的文件级备份双管齐下。我对备份的态度是无论哪种模式必须有至少一份异地或异机备份。本地优先工具的悖论是数据都在本地一旦硬盘坏了数据就真的没了。Notion 虽然让人不放心但起码它的服务器挂了数据还在。自托管意味着备份责任完全在自己身上这一点想清楚再动手。3. Docker 部署实操从拉镜像到正式使用3.1 最快路径五分钟跑起单容器版先走一遍单容器流程。我用的是stable标签稳定版比latest少了些踩雷风险。直接执行mkdir -p ~/.affine docker run -d \ --name affine \ --restart unless-stopped \ -p 3010:3010 \ -v ~/.affine:/root/.affine \ -e AFFINE_ENVproduction \ -e AFFINE_SERVER_HOST0.0.0.0 \ -e AFFINE_SERVER_PORT3010 \ -e AFFINE_SERVER_EXTERNAL_ORIGINhttp://localhost:3010 \ ghcr.io/toeverything/affine:stable逐条解释一下参数。-p 3010:3010把容器的 3010 端口映射到宿主机AFFiNE 默认 Web 端口就是 3010浏览器直接访问宿主机 IP 加这个端口就行。-v ~/.affine:/root/.affine是数据持久化的关键容器内的工作区数据目录映射到宿主机的~/.affine这样容器删了重建数据还在。AFFINE_ENVproduction告诉应用以生产模式运行如果不设置某些开发阶段的接口会暴露出来有安全隐患。启动之后先看日志docker logs -f affine看到类似Listening on 0.0.0.0:3010的输出说明服务起来了。浏览器访问http://服务器IP:3010第一件事是注册管理员账号。AFFiNE 的第一个注册用户默认会成为实例管理员可以管理成员和设置。这一点和很多自托管系统一样抢在别人前面注册会有管理员权限自己在私有部署里不存在这个问题但要注意别把端口暴露到公网然后被别人抢注。数据验证也很简单进入容器看看数据目录docker exec -it affine ls -lh /root/.affine能看到affine.dbSQLite 文件以及files之类的附件目录说明数据已经开始落盘。接下来断网也能访问因为服务跑在本地数据也在本地。3.2 进阶方案Docker Compose 全家桶部署如果确认要长期使用或者有团队协作需求我建议迁到 Compose 全家桶。写一份docker-compose.yml如下这套配置是我基于官方示例和实际部署经验整理出来的生产使用前建议再参考一下官方仓库的最新配置version: 3.8 services: affine-api: image: ghcr.io/toeverything/affine:stable container_name: affine-api command: [node, ./scripts/self-host-predeploy.js] restart: unless-stopped ports: - 3010:3010 environment: - AFFINE_ENVproduction - AFFINE_SERVER_HOST0.0.0.0 - AFFINE_SERVER_PORT3010 - AFFINE_SERVER_EXTERNAL_ORIGINhttps://notes.yourdomain.com - AFFINE_DATABASE_URLpostgresql://affine:affine_passwordaffine-postgres:5432/affine - AFFINE_REDIS_SERVER_HOSTaffine-redis - AFFINE_REDIS_SERVER_PORT6379 - AFFINE_REDIS_SERVER_DATABASE0 volumes: - affine_data:/root/.affine depends_on: affine-postgres: condition: service_healthy affine-redis: condition: service_healthy affine-postgres: image: postgres:16-alpine container_name: affine-postgres restart: unless-stopped environment: - POSTGRES_USERaffine - POSTGRES_PASSWORDaffine_password - POSTGRES_DBaffine volumes: - postgres_data:/var/lib/postgresql/data healthcheck: test: [CMD-SHELL, pg_isready -U affine] interval: 10s timeout: 5s retries: 5 affine-redis: image: redis:7-alpine container_name: affine-redis restart: unless-stopped command: [redis-server, --appendonly, yes] volumes: - redis_data:/data healthcheck: test: [CMD, redis-cli, ping] interval: 10s timeout: 5s retries: 5 volumes: affine_data: postgres_data: redis_data:这套配置里affine-api是主服务affine-postgres是 PostgreSQL 数据库affine-redis是 Redis 缓存和同步通道。注意depends_on里我加上了condition: service_healthy意思是等数据库和 Redis 健康检查通过后才启动主服务避免应用启动时连不上数据库导致反复重启。数据库连接串里的密码建议改掉不要用我示例里的弱密码。还有affine-api的AFFINE_SERVER_EXTERNAL_ORIGIN我写了https://notes.yourdomain.com这个地址必须改成你自己的实际访问域名或 IP。如果填错了后续的分享链接、OAuth 回调地址都会有问题。启动全家桶docker compose up -d观察状态docker compose ps docker compose logs -f affine-api数据不再落在 SQLite 里而是进了 PostgreSQL。可以顺手验证一下docker exec -it affine-postgres psql -U affine -d affine -c \dt能看到一系列 AFFiNE 的数据库表说明业务数据已经切到 PostgreSQL 了。3.3 反向代理与 HTTPS 配置不管是单容器还是全家桶AFFiNE 自带的 HTTP 服务都不建议直接暴露公网。原因有两个一是明文传输账号密码和数据在网络上裸奔二是端口直接暴露容易被扫描器盯上到处打日志。正确做法是套一层反向代理终止 HTTPS然后把请求转发到 AFFiNE 的 3010 端口。Caddy 是配置最省心的选择自动申请和续期证书一个 Caddyfile 就能搞定notes.yourdomain.com { reverse_proxy 127.0.0.1:3010 }Caddy 会自动申请 Let’s Encrypt 证书无需手动管理。如果你之前用过 Nginx也完全可以用 Nginx 写对应的 location 配置并配证书只是证书续期需要自己处理 cron。这里分享一个我踩过的坑加反向代理之后AFFiNE 上的实时协同有时会断开。原因是 WebSocket 连接也需要被代理转发Caddy 默认对reverse_proxy已经支持 WebSocket不需要额外配置。但如果你用的是 Nginx必须在 location 里显式加上升级请求头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; }缺了Upgrade和Connection这两行AFFiNE 的协同编辑会异常但普通页面浏览又正常非常坑。我当时排查了很久才发现是这个问题。3.4 升级流程与数据迁移自托管和官方云服务不一样升级得自己动手。AFFiNE 团队发版比较活跃我习惯每两周到一个月看一次 release 说明再决定要不要升级。单容器升级很简单先备份数据目录然后拉新镜像、删旧容器、重启新容器。命令行如下# 备份 cp -r ~/.affine ~/.affine_backup_$(date %Y%m%d) # 拉新镜像 docker pull ghcr.io/toeverything/affine:stable # 重建容器 docker stop affine docker rm affine docker run -d \ --name affine \ --restart unless-stopped \ -p 3010:3010 \ -v ~/.affine:/root/.affine \ -e AFFINE_ENVproduction \ -e AFFINE_SERVER_HOST0.0.0.0 \ -e AFFINE_SERVER_PORT3010 \ ghcr.io/toeverything/affine:stable全家桶升级更简单因为 Compose 文件定义好了所有服务只需要拉镜像并重建docker compose pull docker compose up -d注意先看 release notes如果涉及数据库结构变更升级后可能需要跑迁移脚本。AFFiNE 官方镜像里通常已经内置了迁移逻辑容器启动时会自动处理但保险起见升级前一定做 PostgreSQL dump。PostgreSQL 备份命令docker exec -t affine-postgres pg_dump -U affine affine affine_backup_$(date %Y%m%d).sql备份文件再拷贝到另一台机器存一份这才算真正安全。4. 三大核心场景实测文档、白板、数据库在一个画布里干活4.1 文档模式我能直接替换 Notion 吗部署完成之后第一件事是导入我旧笔记里的部分内容实测文档体验。AFFiNE 的编辑器基础能力很完整Markdown 语法输入、标题层级、目录、代码块高亮、Latex 公式、图片附件、引用、列表任务勾选该有的都有。从 Notion 迁移过来最明显的区别是布局。Notion 的页面是单栏文档流固定宽度往页面里塞大量信息时版面容易臃肿。AFFiNE 的文档模式默认宽度更宽而且每个块都可以直接拖拽调整位置——虽然文档模式调整位置的意义不大但这种“块就是对象”的感觉很直观。任务型笔记是 AFFiNE 的舒适区。我日常用它维护个人 OKR、周报、技术方案初稿。写方案的时候先切换到 Edgeless 模式把思路画出来再切回文档模式把逻辑写成文字这个流程很流畅。但如果你依赖于 Notion 的海量模板库、数据库高级筛选条件、各种第三方集成插件AFFiNE 暂时还覆盖不了这些生态。它的定位更接近 Notion 的“清爽子集”而不是全功能替代。4.2 Edgeless 画布模式无限画布重新组织信息Edgeless 模式是 AFFiNE 最亮眼的特色相当于在文档外层包了一层无限画布。你可以把文档里的任意块拖到画布上变成可自由移动、缩放、连线的节点。画布本身也支持直接创建图形、手绘线条、便签、连接线。我用得比较多的场景是画系统架构图。以前我会专门开一个绘图工具画完截图贴进文档改一次截图一次版本同步非常麻烦。现在直接在 Edgeless 模式里画架构图里的服务节点如果和我文档里的技术方案段落是同一个块那么改了文档内容画布上的节点会自动更新。这个特性解决了一个很实际的问题图与文不再分部维护。还有一点值得提Edgeless 模式里的节点可以承载数据库视图。我可以把一个数据库看板拖到画布上它就是一个实时刷新的数据窗口。比如在做项目复盘的时候左边放着周报文档中间画着时间线连线右边嵌着一个任务状态数据库所有信息围绕同一个话题在同一个空间里呈现不用来回翻页面。4.3 数据库模式表格、看板、画廊视图AFFiNE 的数据库能力虽然不至于叫板 Airtable但对个人和小团队完全够用。支持创建表格视图、看板视图、画廊视图每种视图呈现的是同一份数据只是展示形式不同。我用它维护最多的是家庭设备信息库。表格视图记录设备名称、型号、购买日期、保修到期日、IP 地址看板视图按设备类型分组一目了然。这个用法放在 Notion 里也能实现不算 AFFiNE 的差异化优势。真正的差异在于这个数据库可以嵌入到任何文档或白板的任意位置。比如我在写家庭网络改造方案时Edgeless 画布上画了网络拓扑图旁边直接嵌入设备信息数据库图和数据同时可见不用再开两个页面。数据库字段类型覆盖了文本、数字、单选、多选、日期、成员、链接、文件日常使用够用。复杂筛选和公式能力相对薄弱如果你重度依赖 Notion 数据库的高级属性和跨库关联AFFiNE 目前可能会有落差。我的建议是把数据库当“轻量数据容器”用不要当全套 Airtable 替代品。4.4 三者结合的实际工作流一次真实需求处理的全程记录说一个我最近实际跑通的流程可以直观看到三合一的价值。一个客户提出要新增一个数据导出功能。我打开一个新页面先用文档模式把客户需求原话粘贴进来拆成几个关键块。切换到 Edgeless把需求卡片拖开画了一条从“数据选择”到“导出格式”再到“生成文件”的流程线把技术方案随手写成便签贴在流程节点旁边。接着在画布上新建一个数据库字段设为任务名、负责人、优先级、状态把需求拆成的几个子任务一条条录入。到此为止一页里同时有了需求原文、方案图、任务跟踪板。第二天开会同步进度我不需要新建任何页面直接打开这一页把 Edgeless 模式切换成文档模式画布上的卡片自动排成文档流方案图和任务表顺序呈现投影出来就是一份工整的会议纪要。这个流程在 Notion 时代至少需要三个页面需求文档页、架构图工具页、任务管理页。三页之间互相跳转信息容易丢失。AFFiNE 把“思考过程”和“最终呈现”统一在了一个连续空间里这是我个人感知最深的效率提升。5. 常见问题与排查技巧实录5.1 容器启动失败或反复重启新部署后第一次启动容器总是重启先看日志再定位docker logs affine最常见的有三种情况。端口被占用listen EADDRINUSE这种日志说明 3010 端口被其他程序占了改映射端口就行-p 3011:3010即可。数据库连接失败全家桶部署时主服务日志里出现ECONNREFUSED大概率是depends_on没有等待数据库健康检查重新检查 Compose 里healthcheck和condition配置。权限问题日志里出现EACCES通常是对挂载目录没有写权限执行chown -R 1000:1000 ~/.affine或调整宿主目录权限。5.2 数据持久化配置错误导致数据丢失这是最危险的坑。如果你启动容器时忘了挂载-v ~/.affine:/root/.affine容器写入的数据都在容器可写层里。容器还在运行时不觉得有问题一旦docker rm删掉容器整个工作区数据跟着蒸发。我见过不止一个朋友栽在这上面。判断是否挂载成功的办法是进容器看数据目录docker exec -it affine ls -la /root/.affine如果你在宿主机~/.affine里看不到对应内容说明 volume 没挂对立即停下当前操作排查挂载参数因为在错误挂载下继续使用只会积重难返。5.3 编辑性能卡顿与优化AFFiNE 是浏览器端渲染的应用性能瓶颈一般在浏览器侧。如果你在一个超大 Edgeless 画布上放了几百个节点拖动和缩放都会卡。单容器模式下 SQLite 的写入在高频操作时也会有轻微延迟。我实际验证过的优化方案大画布前先组织内容用 Frame框选分组把相关节点封装成组减少画布上的散点数量长时间编辑后刷新页面释放浏览器内存自托管机器内存不足导致容器被 OOM Killer 杀掉时给 Docker 分配更多内存或者给容器加--memory限制并预留系统 slot。还有升级镜像版本AFFiNE 每个版本基本都在优化渲染性能这是最省力的优化方案。5.4 从 Notion 迁移的取舍建议卸载 Notion 之前迁移是个现实问题。AFFiNE 支持导入 Notion 的 Markdown / CSV 导出文件但效果只能说“能进来”不能保证排版完全还原。Notion 导出的一大堆附件文件和复杂数据库关系自动导入后经常缺胳膊少腿。我的建议是不要试图一键整体迁移把 Notion 当“冷数据仓库”只把当前正在活跃使用的页面手动复制到 AFFiNE。历史低频内容留着在 Notion 那边只读访问等确实要用了再逐篇搬。这样迁移成本低也不用担心数据转换出问题。等 AFFiNE 侧的内容跑顺几个月再决定历史数据怎么处理比较好。5.5 常见问题速查表问题现象可能原因处理方法容器反复重启端口被占用或数据库未就绪查看日志释放端口或等待健康检查页面能打开但登录一直失败AFFINE_ENV 未设为 production检查环境变量重启容器协同编辑连接中断反向代理未支持 WebSocketNginx 补上 Upgrade 和 Connection 头上传的图片附件找不到数据卷未挂载或权限不足检查 volume 配置和目录权限无法注册第二个账号单容器默认单用户模式/管理员限制参考官方文档调整用户策略数据全部消失容器删除但未持久化恢复之前的数据目录备份重新挂载写在最后切换到 AFFiNE 之后的个人体会部署和用了两个月之后我的整体评价是AFFiNE 在“文档 白板 数据库”这三合一的路上走得比我想象中扎实虽然数据库能力和 Notion 比还有差距第三方生态也才刚刚起步但它真正解决了我在 Notion 时代最痛的问题——信息碎片化导致的上下文丢失。我个人的选择是生产相关的方案文档、项目看板、知识库日常维护全部放在 AFFiNE 自托管实例上Notion 只保留了历史归档不再作为主用工具。如果你也在犹豫要不要自托管 AFFiNE最后给你两个小建议第一第一轮部署先用单容器版别急着上全家桶把核心功能体验过了再决定要不要升级架构第二自托管最重要的是备份给~/.affine目录做定时异地备份这比折腾任何花哨功能都重要。
返回列表