AFFiNE 离线部署 · Docker Compose · 指令版

第 1 章 名词认一遍

|------------------|--------------------------|
| 名词 | 一句话解释 |
| Docker | 运行容器的引擎 |
| 镜像 | 程序打包好的"毛坯房" |
| 容器 | 镜像跑起来的实例 |
| Compose | 用一个 YAML 管理多个容器 |
| bind mount | 宿主机目录挂进容器;删容器不删目录 |
| named volume | Docker 管理的卷;down -v 会删 |
| migration | 启动前一次性升级数据库的任务,退出码必须 0 |
| externalUrl | AFFiNE 对外地址,影响附件与登录回调 |
| healthcheck | 容器的健康检查,healthy 才算就绪 |


第 2 章 准备机器、材料、变量

2.1 检查机器

vb 复制代码
`uname -m                                             
nproc
free -h
df -h /
ip -o -4 addr show scope global`

【你应该看到】

  • x86_64
  • CPU ≥ 2(建议 4)
  • 内存 total ≥ 3.5Gi
  • 根分区可用 ≥ 30G
  • 一个全局 IPv4(抄下来)

2.2 确认系统

复制代码
. /etc/os-release && echo "$PRETTY_NAME"

【你应该看到】Ubuntu 22.04.x LTS。

2.3 检查材料

复制代码
cd ~/offline
ls -lh affine-images.tar affine/docker-compose.yml affine/config/config.json       //这里主要是检查镜像包是不是大约468mb,如果只有几kb,说明下载的时候文件损坏了;同时确定docker的启动脚本(docker-compose.yml)和 AFFiNE 的配置文件(config.json)在正确的位置;
ls -d docker-debs             //因为我们的要求是进行离线下载,所以我们需要提前下载好docker的离线安装包放在这个文件夹里,这一步就是检查这个宝箱在不在;
ls ~/offline/docker-debs/*.deb | wc -l              //统计我们的宝箱的数量;

【你应该看到】

复制代码
affine-images.tar          约 468M
affine/docker-compose.yml
affine/config/config.json
docker-debs/
64

镜像条目:

复制代码
tar -xOf affine-images.tar manifest.json | python3 -c '       //找到一个叫manifest.json的文件,打印上面的文字;
import json,sys
d=json.load(sys.stdin)                                       //接过前面递过来的上面的json代码,数一数有几个物品;
print("条目数:", len(d))                                      //打印物品的个数
for x in d:
    print(" ", ", ".join(x.get("RepoTags") or []))
'

【你应该看到】条目数: 4,包含:

ghcr.io/toeverything/affine:stable、redis:7.2.4、redis:latest、pgvector/pgvector:pg16。

2.4 确认基础命令

这里主要是用来检查我们的工具全不全,如:bash , curl , tar 和 gzip , ss , ip

复制代码
bash (命令行外壳): 你的地基。你现在敲打所有命令的黑框框环境就是它提供的。
curl (网络探测器): 用来模拟浏览器发请求。
tar和gaip (打包/解压工具)
ss(端口雷达): 巡逻兵。文档里用它来检查本机的 3010 端口
ip

for t in bash curl tar gzip python3 ss ip; do
  printf '%-10s ' "$t"
  command -v "$t" || echo MISSING
done

【你应该看到】没有 MISSING。

2.5 变量块(每新开终端都要执行)

把 LAN_IP 改成你的内网 IP,然后整段复制执行:

export指令在这里的作用主要是将这些变量的属性改为全局变量;

复制代码
export OFFLINE=$HOME/offline
export AFFINE_DIR=$OFFLINE/affine
export AFFINE_CFG=$AFFINE_DIR/config/config.json
export AFFINE_PORT=3010
export LAN_IP=192.168.72.128

逐行解释:

|---------------|-------------|--------------------|
| 变量 | 作用 | 能不能改 |
| OFFLINE | 材料根目录 | 材料换路径时改 |
| AFFINE_DIR | AFFiNE 部署目录 | 一般不改 |
| AFFINE_CFG | 配置文件路径 | 一般不改 |
| AFFINE_PORT | 对外端口 | 与 compose 的端口映射一起改 |
| LAN_IP | 本机内网 IP | 必须改成你的 |

【判据】

复制代码
echo "$LAN_IP / $AFFINE_PORT"
ls -d "$AFFINE_DIR" "$AFFINE_CFG"

【你应该看到】

复制代码
192.168.72.128 / 3010
/home/你的用户名/offline/affine
/home/你的用户名/offline/affine/config/config.json

2.6 断网(建议)

复制代码
sudo ip route del default
curl -I --max-time 5 https://ghcr.io 2>&1 | head -3

【你应该看到】Could not resolve host / Network is unreachable 之类失败。

【注意】浏览器要访问内网 IP 时,不要停网卡,只删默认路由。


第 3 章 离线安装 Docker(手工)

3.1 确认 deb 包

复制代码
ls ~/offline/docker-debs/*.deb | wc -l

【你应该看到】64(至少 ≥ 4)。

3.2 用 apt 本地安装

复制代码
cd ~/offline/docker-debs
sudo apt-get install -y --no-download ./*.deb           

【你应该看到】Setting up docker.io ...、Setting up docker-compose-v2 ... 等。

--no-download 的作用:禁止 apt 联网补包;本地包不全时会直接失败,而不是偷偷下载。

3.3 如果 apt 失败,用 dpkg 回退

dpkg(Debian Package Manager)是 Ubuntu 系统里最底层的软件安装工 。它专门负责把 .deb 后缀的安装包解压、写进系统。

【这里为什么要装3次呢?】

因为 dpkg 是按字母顺序盲装的,所以一定会遇到"依赖顺序颠倒"的问题。

复制代码
cd ~/offline/docker-debs
sudo dpkg -i ./*.deb || true
sudo dpkg -i ./*.deb || true
sudo dpkg -i ./*.deb || true

装 3 轮是因为包的依赖顺序未必刚好;后面几轮会把前面缺的依赖补上。

【你应该看到】最后一轮基本没有 dependency problems。

3.4 启动服务

这里是将containerd和 docker进行启动并常驻后台;

复制代码
sudo systemctl enable --now containerd             //systemctl: 它是 Linux 系统的"大管家",专门负责管理后台程序。    
                                                  //enable(开机自启)
                                                  //--now(立即生效)
sudo systemctl enable --now docker
sleep 3

3.5 验证

复制代码
docker version
docker compose version

【你应该看到】

复制代码
Client: Docker Engine ...
Server: Docker Engine ...
...
Docker Compose version v2.40.x

【如果当前用户 permission denied】

Docker 是一个权限极高的底层系统软件。为了安全,Linux 默认只允许超级管理员(root)去碰这个发动机。

复制代码
newgrp docker

或者重新登录一次。

3.6 加入 docker 组(新用户需要)

复制代码
sudo usermod -aG docker "$USER"
newgrp docker

【判据】

这里的ps是用来查看进程相关的信息的;

复制代码
docker ps

【你应该看到】空表或已有容器列表,不再报 permission denied。

第 4 章 导入镜像

【这一步做什么】把 tar 里的 4 个镜像导入本机 Docker。

【为什么】离线环境不能联网拉取;不导入,启动时会报 pull access denied。

【命令】

复制代码
docker load -i "$OFFLINE/affine-images.tar"      //docker load:是docker专用的离线安装的指令
//-i 这里是input输入的意思后面跟的是文件路径;
//"$OFFLINE/affine-images.tar":这就是提前准备好的、装了 4 个核心镜像的那个 468MB 的大压缩包的完整路径。

【你应该看到】

复制代码
Loaded image: ghcr.io/toeverything/affine:stable
Loaded image: redis:7.2.4
Loaded image: redis:latest
Loaded image: pgvector/pgvector:pg16

【判据】

复制代码
docker images --format '{{.Repository}}:{{.Tag}}\t{{.Size}}' \
  | grep -E 'affine|pgvector|redis'

【你应该看到】至少这 3 类:

复制代码
ghcr.io/toeverything/affine:stable   895MB
pgvector/pgvector:pg16               ...
redis:7.2.4                          ...
redis:latest                         ...

【如果不对】重新 docker load;确认 tar 没损坏(大小约 468M)。


第 5 章 改写对外地址

【这一步做什么】把 config.json 的 externalUrl 从 localhost 改成你的内网 IP。

【为什么】AFFiNE 用它生成分享链接、附件地址、登录回调;写 localhost 时别的电脑打不开。

【命令:先备份】

复制代码
cd "$AFFINE_DIR"
cp -a config/config.json "config/config.json.bak.$(date +%Y%m%d-%H%M%S)"
ls -1t config/config.json.bak.* | head -3

【你应该看到】至少一个 config.json.bak.2026... 文件。

【命令:改写】

复制代码
python3 - "$AFFINE_CFG" "http://$LAN_IP:$AFFINE_PORT" <<'PY'
import re, sys
path, url = sys.argv[1], sys.argv[2]
raw = open(path, encoding="utf-8").read()
new, n = re.subn(r'("externalUrl"\s*:\s*)"[^"]*"', r'\1"%s"' % url, raw)
if n == 0:
    sys.exit("config.json 里没找到 externalUrl 字段")
open(path, "w", encoding="utf-8").write(new)
print(f"已改写 {n} 处 -> {url}")
PY

【你应该看到】

复制代码
已改写 1 处 -> http://192.168.72.128:3010

【判据】

复制代码
grep -n externalUrl "$AFFINE_CFG"

【你应该看到】

复制代码
5:    "externalUrl": "http://192.168.72.128:3010",

【如果不对】

  • 报 没找到 externalUrl:打开文件确认字段名与格式:

    grep -n 'external' "$AFFINE_CFG"

  • 手工改也可以:用编辑器把 http://localhost:3010 改成 http://<LAN_IP>:3010。


第 6 章 启动容器

【这一步做什么】用 docker compose up 启动 4 个容器。

【为什么】--pull never 是离线死命令:只用本地镜像;缺镜像立刻报错,不会联网卡住。

【命令】

复制代码
cd "$AFFINE_DIR"
mkdir -p data
docker compose up -d --pull never      // up:启动
                                       // -d: 后台运行
                                       // --pull never(离线护身符): 强制命令 Docker "永远不要(never)去网上拉取(pull)镜像"

【你应该看到】

复制代码
 Network affine_default  Created
 Container affine_postgres  Started
 Container affine_redis     Started
 Container affine_migration_job  Started
 Container affine_server    Started

【判据:容器状态】

复制代码
docker compose ps

【你应该看到】

复制代码
NAME                     IMAGE                                STATUS
affine_migration_job     ghcr.io/toeverything/affine:stable   exited (0)
affine_postgres          pgvector/pgvector:pg16               running (healthy)
affine_redis             redis:latest                         running (healthy)
affine_server            ghcr.io/toeverything/affine:stable   running

第 7 章 验证

7.1 迁移任务

复制代码
docker inspect -f '{{.State.ExitCode}}' affine_migration_job

【你应该看到】0。

7.2 容器健康

复制代码
docker compose ps

【你应该看到】

  • affine_postgres:running (healthy)
  • affine_redis:running (healthy)
  • affine_server:running
  • affine_migration_job:exited (0)

7.3 入口层

复制代码
curl -s -o /dev/null -w 'AFFiNE:3010 -> %{http_code}\n' http://127.0.0.1:3010/

【你应该看到】AFFiNE:3010 -> 302(或 200)。

7.4 日志层

复制代码
for c in affine_server affine_postgres affine_redis; do
  n=$(docker logs "$c" 2>&1 \
      | grep -ciE "dial tcp|no such host|i/o timeout|ENOTFOUND|getaddrinfo" || true)
  printf '%-24s 网络失败线索: %s\n' "$c" "$n"
done

【你应该看到】基本为 0。

【例外】127.0.0.1:587 连接拒绝 = SMTP 探测,假警报。

7.5 数据落盘检查

复制代码
ls -la "$AFFINE_DIR/data"
du -sh "$AFFINE_DIR/data/storage" "$AFFINE_DIR/data/postgres" 2>/dev/null

【你应该看到】两个目录存在,postgres 目录里有内容(说明数据库初始化成功)。


第 8 章 浏览器首次初始化

8.1 打开

复制代码
http://<LAN_IP>:3010

注意是 http,不是 https。

8.2 首次向导

  1. 设置管理员邮箱与密码。
    • 邮箱必须是合法域名:@example.com 可以,@test.local 会被拒。
  1. 点 Continue。
  2. 看到 All Settings Done 后再点一次 Continue。

8.3 验证数据持久化

  1. 新建文档
  2. 输入文字
  3. 刷新页面
  4. 内容还在

8.4 用命令行确认数据真的落盘

复制代码
ls -la ~/offline/affine/data/storage

【你应该看到】出现新的目录/文件(按时间排序最新)。


相关推荐
lhh_qrsly1 天前
TI CCS(Code Composer Studio,德州仪器嵌入式 IDE)2015~2026 版本总览
ide·php·composer
传奇开心果编程13 天前
【声明式UI开发实用技术学与练】第8课 状态提升与下放
学习·flutter·react native·ui·swiftui·composer
传奇开心果编程14 天前
【Compose Multiplatform 跨端开发学与练】第7课 平台适配与互操作
android·windows·学习·ui·ios·kotlin·composer
传奇开心果编程14 天前
【Compose Multiplatform 跨端开发学与练】第5课 网络与数据层
android·网络·学习·ui·ios·kotlin·composer
传奇开心果编程15 天前
【Compose Multiplatform 跨端开发学与练】第3课 布局与组件
android·windows·学习·ui·ios·kotlin·composer
传奇开心果编程15 天前
【Compose Multiplatform 跨端开发学与练】第8课 资源管理与主题
android·windows·学习·ios·kotlin·web·composer
传奇开心果编程15 天前
【Compose Multiplatform 跨端开发学与练】第9课 测试与调试
android·学习·macos·ios·kotlin·web·composer
传奇开心果编程15 天前
【Compose Multiplatform 跨端开发学与练】第4课 导航与路由
android·windows·学习·ui·ios·kotlin·composer
传奇开心果编程15 天前
【Compose Multiplatform 跨端开发学与练】第6课 状态管理与架构
android·学习·ui·ios·架构·kotlin·composer