OpenFic 部署全过程

3531 字
18 分钟
OpenFic 部署全过程

关于 OpenFic 的简介,请访问原项目链接。本篇仅记录我在 VPS 上部署自托管 OpenFic 的全过程。

我的部署流程大致分为两个阶段:

  1. 第一阶段:直接部署官方 repo
  2. 第二阶段:部署自己 fork 的版本(添加了一些新功能支持)

1. 官方版本部署#

由于我的 VPS 上先前已经部署了 Dcomost,所以对于 OpenFic,我的部署方案结构大致为:

VPS
│
Docker Caddy
:80 / :443
/ \
/ \
story.example.com fiction.example.com
│ │
▼ ▼
docmost:3000 openfic:8000
│
┌───┴────┐
▼ ▼
PostgreSQL Redis

这里简要介绍一下 Docker Caddy:

由于先前的 Docmost 已经引入了一个 Caddy,所以这里就直接复用了先前的。

1.1 前期工作#

1.1.1 新增子域名#

在此之前,我已经有了一个 Dcomost 的子域名:

A story 1.2.3.4

这个域名我们保留不动,然后再添加:

A fiction 1.2.3.4

于是:

story.example.com → 1.2.3.4
fiction.example.com → 1.2.3.4

然后我们可以检查:

Terminal window
getent ahostsv4 fiction.example.com

这条指令应该返回我们的 VPS 公网 IP。

而关于公网端口的开放,下列端口

22
80
443

已经足够。

1.1.2 建立 Caddy 和 OpenFic 的共享 Docker 网络#

执行:

Terminal window
sudo docker network create reverse_proxy

确认:

Terminal window
sudo docker network ls

我们应该看到:

reverse_proxy

1.2 修改现有 Docmost Compose#

由于我先前已经部署了 Docmost,所以我先进入:

Terminal window
cd ~/docmost

编辑:

Terminal window
nano docker-compose.yml

我当时的 Caddy 大概是:

services:
caddy:
image: caddy:2
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
depends_on:
- docmost
restart: unless-stopped

将其修改成:

services:
caddy:
image: caddy:2
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
depends_on:
- docmost
restart: unless-stopped
networks:
- default
- reverse_proxy

这里的:

- default

非常重要,因为 Caddy 仍然需要访问:

docmost:3000

然后在这个 Compose 文件最底部,当时已经有:

volumes:
docmost:
db_data:
redis_data:
caddy_data:
caddy_config:

在它后面添加:

networks:
reverse_proxy:
external: true

所以结尾应该类似:

volumes:
docmost:
db_data:
redis_data:
caddy_data:
caddy_config:
networks:
reverse_proxy:
external: true

检查:

Terminal window
sudo docker compose config -q

没有输出就说明 YAML 基本正常。

1.3 让现有 Caddy 加入新网络#

应用修改:

Terminal window
sudo docker compose up -d caddy

检查:

Terminal window
sudo docker compose ps

应该仍然:

caddy Up
docmost Up
db Up
redis Up

再确认 Docmost 没受影响:

Terminal window
curl -fsS https://story.example.com/api/health

正常的话说明我们目前的修改成功。

1.4 生成 OpenFic 密码#

和 Docmost 类似,我也创建了一个 ~/openfic 目录,与 ~/docmost 平级,我的目录结构类似:

~
├── docmost/
│ ├── Caddyfile
│ └── docker-compose.yml
│
└── openfic/
├── .env
├── docker-compose.yml
└── data/

接着我们执行:

Terminal window
openssl rand -base64 36

这一步生成的就是一个随机的强密码,当然你也可以用自己的,我们复制输出结果。

然后:

Terminal window
vi ~/openfic/.env

填写:

OPENFIC_AUTH_PASSWORD=这里换成刚生成的密码

例如:

OPENFIC_AUTH_PASSWORD=VJKy2pFTZ7A9VdgJmF9r2CVg8SLwKUZmP9C8X7fhdnL7F9hJ

保存后:

Terminal window
chmod 600 ~/openfic/.env

如果不配置这个变量,应用级密码保护就是关闭状态。也就是说,任何人访问你的域名都能进入你的系统。

1.5 创建 OpenFic Compose#

执行:

Terminal window
cd ~/openfic
vi docker-compose.yml

写入:

services:
openfic:
image: ghcr.io/syrizelink/openfic:latest
container_name: openfic
restart: unless-stopped
environment:
OPENFIC_DATA_DIR: /data
OPENFIC_AUTH_PASSWORD: "${OPENFIC_AUTH_PASSWORD}"
# Caddy 位于另一个 Docker 网络地址,
# 允许 Uvicorn 接受反向代理提供的 HTTPS 信息。
FORWARDED_ALLOW_IPS: "*"
volumes:
- ./data:/data
networks:
- reverse_proxy
networks:
reverse_proxy:
external: true

我们让 OpenFic 官方镜像本身在容器内监听 0.0.0.0:8000,同时把 /data 设置成其持久化数据目录。

检查:

Terminal window
sudo docker compose config -q

1.6 启动 OpenFic 并验证 Caddy 能不能找到 OpenFic#

首先:

Terminal window
cd ~/openfic
sudo docker compose pull

然后:

Terminal window
sudo docker compose up -d

检查:

Terminal window
sudo docker compose ps

应该类似:

NAME IMAGE STATUS
openfic ghcr.io/syrizelink/openfic:latest Up

查看日志:

Terminal window
sudo docker compose logs --tail=100 openfic

如果想实时查看:

Terminal window
sudo docker compose logs -f openfic

退出日志查看:

Ctrl+C

因为现在 OpenFic没有映射宿主机端口,所以我们不能再:

Terminal window
curl 127.0.0.1:8000

可以直接从 Caddy 容器测试 Docker 网络:

Terminal window
cd ~/docmost
sudo docker compose exec caddy \
wget -qO- http://openfic:8000/api/v1/health

正常的话应该看到类似:

{"status":"healthy","version":"..."}

如果成功,说明:

Caddy → OpenFic

这条链路已经打通。

1.7 修改现有的 Caddyfile#

现在回到:

Terminal window
cd ~/docmost
vi Caddyfile

我当时应该类似:

story.example.com {
reverse_proxy docmost:3000
}

这是我原来的部署方式。

然后我将其改成:

story.example.com {
reverse_proxy docmost:3000
}
fiction.example.com {
reverse_proxy openfic:8000
}
Info

将两个域名替换成你自己的真实域名。

1.8 检查并重新加载 Caddy#

先验证:

Terminal window
cd ~/docmost
sudo docker compose exec caddy \
caddy validate \
--config /etc/caddy/Caddyfile

如果看到类似:

Valid configuration

重新加载:

Terminal window
sudo docker compose exec caddy \
caddy reload \
--config /etc/caddy/Caddyfile

然后我们就可以访问 OpenFic,用浏览器打开:

https://fiction.example.com

Caddy 会负责:

DNS
↓
VPS :443
↓
Caddy
↓
openfic:8000

并自动为新的:

fiction.example.com

申请 HTTPS 证书。

然后 OpenFic 会要求输入:

OPENFIC_AUTH_PASSWORD

设置的密码。

它验证成功后会生成 HTTP-only Cookie;通过 HTTPS 使用时会设置 Secure Cookie。

1.8.1 如果出现错误#

这一步我遇到了一个问题,就是浏览器访问我的域名显示:

此网站无法提供安全连接
【我的域名】发送的响应无效。
ERR_SSL_PROTOCOL_ERROR

如果遇到了这个问题,可以按下面步骤排查:

Terminal window
# 1. 原来的 Docmost 是否正常
curl -I https://story.你的域名
# 2. 新域名解析到了哪里
getent ahostsv4 fiction.你的域名
getent ahostsv6 fiction.你的域名
# 3. 查看实际 Caddyfile
cd ~/docmost
cat Caddyfile
# 4. 验证并重新加载
sudo docker compose exec caddy \
caddy validate --config /etc/caddy/Caddyfile
sudo docker compose exec caddy \
caddy reload --config /etc/caddy/Caddyfile
# 5. 看证书申请日志
sudo docker compose logs --tail=200 caddy
# 6. 测试 HTTPS 握手
curl -vkI https://fiction.你的域名

你可以直接把上面指令的输出贴给 AI 让它帮你查问题。就我的经历来说,我问题的根因是:虽然在 ~/docmost/Caddyfile 里已经包含了新的 OpenFic 域名,但正在运行的 Caddy 容器实际加载的仍然是旧版 Caddyfile,也就是只包含 Docmost 的域名。

以我的 VPS 为例,我执行

Terminal window
cd ~/docmost
sudo docker compose exec caddy \
cat /etc/caddy/Caddyfile

然后就发现只有

story.example.com {
reverse_proxy docmost:3000
}

这里我们就需要重新创建 Caddy 容器:

Terminal window
cd ~/docmost
sudo docker compose up -d --force-recreate caddy

这样,新的容器就会重新执行:

宿主机 ./Caddyfile
↓
bind mount
↓
/etc/caddy/Caddyfile

1.9 以后两个项目的管理方式#

这是采用两个 Compose 项目最大的好处。

以我的 VPS 为例,如果要管理 Docmost:

Terminal window
cd ~/docmost
sudo docker compose ps
sudo docker compose logs -f
sudo docker compose restart docmost

管理 OpenFic:

Terminal window
cd ~/openfic
sudo docker compose ps
sudo docker compose logs -f
sudo docker compose restart openfic

更新 OpenFic:

Terminal window
cd ~/openfic
sudo docker compose pull
sudo docker compose up -d

这样,两边就互不影响。唯一共用的组件是就 Caddy,因此以后不用在 ~/openfic 中再创建 Caddy 容器。

2. Fork 版本部署#

2.1 记录已有配置#

我们先找到当前 OpenFic 容器,并记录现有配置。查看所有运行中的容器:

Terminal window
docker ps

或者只显示比较有用的信息:

Terminal window
docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}'

我们应该能看到类似:

NAMES IMAGE STATUS PORTS
openfic ghcr.io/syrizelink/openfic:latest Up 3 days 0.0.0.0:8000->8000/tcp

下面假设容器名就是:

Terminal window
openfic

如果你的实际容器名不同,把后面命令里的 openfic 替换掉。

先记录当前镜像:

Terminal window
docker inspect openfic --format 'Image={{.Config.Image}}'

再记录端口:

Terminal window
docker port openfic

再查看 restart policy:

Terminal window
docker inspect openfic --format 'RestartPolicy={{.HostConfig.RestartPolicy.Name}}'

最后查看环境变量:

Terminal window
docker inspect openfic --format '{{range .Config.Env}}{{println .}}{{end}}'

2.2 确认 data 有没有持久化挂载。#

如果是按照我的第一阶段部署方式,那么数据是持久化的。也就是说,以后更新版本,重新创建容器不会影响服务器端保存的数据。

不过我们还是可以检查一下,运行:

Terminal window
docker inspect openfic --format '{{range .Mounts}}{{printf "Type=%s Name=%s Source=%s Destination=%s\n" .Type .Name .Source .Destination}}{{end}}'

如果你看到类似:

Type=volume Name=openfic Source=/var/lib/docker/volumes/openfic/_data Destination=/data

那么就没啥问题,这表示:

容器 /data
↓
Docker volume: openfic
↓
/var/lib/docker/volumes/openfic/_data

换镜像、换容器都不会自动删除这个 volume。

如果看到:

Type=bind Name= Source=/home/user/openfic-data Destination=/data

也没问题。

这是 bind mount:

服务器 /home/user/openfic-data
↓
容器 /data

同样是持久化的。

但如果这条命令完全没有出现 Destination=/data,就要停止后续部署操作,因为这意味着你的数据可能还在旧容器 writable layer 里,具体的迁移方法可以参考其他教程,或者就具体情况询问 AI.

你还可以专门只检查 /data:

Terminal window
docker inspect openfic --format '{{range .Mounts}}{{if eq .Destination "/data"}}Type={{.Type}} Name={{.Name}} Source={{.Source}}{{end}}{{end}}'

如果按照官方 README 的命令:

Terminal window
docker run -d -p 8000:8000 -v "openfic:/data" --name openfic ghcr.io/syrizelink/openfic:latest

部署,那么正常应该得到:

Type=volume Name=openfic ...

2.3 检查 /data 内的数据是否真的存在。#

运行:

Terminal window
docker exec openfic sh -lc 'ls -lah /data'

正常情况下应该能看到:

.key
openfic.db
checkpoints.db
...

也可以专门检查两个最关键文件:

Terminal window
docker exec openfic sh -lc '
test -s /data/openfic.db && echo "openfic.db: OK" || echo "openfic.db: MISSING";
test -s /data/.key && echo ".key: OK" || echo ".key: MISSING";
'

理想输出:

openfic.db: OK
.key: OK

其中 .key 很重要。OpenFic 启动时会优先读取数据目录里的 .key;如果没有,就会生成一个新的 key。已经保存在数据库里的 API Key 是用旧 key 加密的,因此如果你丢了 .key,旧的 Provider API Key 可能就无法正常解密。

2.4 从你的 GitHub 仓库拉取修改后的源码。#

clone 你的 fork:

Terminal window
git clone https://github.com/YOUR_USERNAME/OpenFic.git OpenFic-custom

然后:

Terminal window
cd OpenFic-custom

检查当前分支:

Terminal window
git branch --show-current

检查最后一次 commit:

Terminal window
git log -1 --oneline

检查工作目录:

Terminal window
git status

理想状态:

nothing to commit, working tree clean

如果你的代码以后已经 clone 过了,就不需要重新 clone:

Terminal window
cd ~/OpenFic-custom
git pull --ff-only

可以把 Docker 镜像 tag 直接和 Git commit 绑定:

Terminal window
TAG=$(git rev-parse --short HEAD)
echo "$TAG"

比如输出:

8f47e21

那么你的镜像就叫:

openfic-custom:8f47e21

这样将来出了问题,我们就可以知道服务器跑的到底是哪版源码。


2.5 在旧 OpenFic 继续运行的情况下,构建新镜像。#

这一步先不碰旧容器,运行:

Terminal window
docker build -t openfic-custom:$TAG .

想看更完整日志可以:

Terminal window
docker build --progress=plain -t openfic-custom:$TAG .

OpenFic 当前 Dockerfile 会先编译前端,然后安装 Python 后端依赖,再复制后端源码,最后把前端产物和 Python 后端一起放进镜像;最终应用仍然把运行数据目录定义为 /data。

有一个地方需要特别注意:Dockerfile 使用了:

uv sync --frozen

因此,如果你修改了:

backend/pyproject.toml

增加或者修改 Python 依赖,却没有同步提交:

backend/uv.lock

构建可能直接失败。

如果你只是改了 OpenFic 自己的 Python 源码,例如 Vertex adapter,而没有新增依赖,通常不会遇到这个问题。

构建完成后检查:

Terminal window
docker images | grep openfic-custom

应该出现:

openfic-custom 8f47e21 ...

2.6 先用空数据启动一次新镜像做 smoke test#

先启动临时测试容器:

Terminal window
docker run -d \
--name openfic-smoke \
-p 18000:8000 \
openfic-custom:$TAG

检查:

Terminal window
docker ps | grep openfic-smoke

查看日志:

Terminal window
docker logs --tail 100 openfic-smoke

测试 HTTP:

Terminal window
curl -I http://127.0.0.1:18000/

或者:

Terminal window
curl -fsS http://127.0.0.1:18000/ >/dev/null && echo "OpenFic smoke test OK"

如果输出:

OpenFic smoke test OK

至少说明我们的镜像能正常启动。

然后删除测试容器:

Terminal window
docker stop openfic-smoke
docker rm openfic-smoke

2.7 正式切换前,停旧容器并完整备份 /data#

这一步可选,如果还没存什么重要数据,或者就自己一个人用,没有多用户的话,也可以直接跳。

创建备份目录:

Terminal window
BACKUP_DIR="$HOME/openfic-backup-$(date +%Y%m%d-%H%M%S)"
mkdir -p "$BACKUP_DIR/data"
echo "$BACKUP_DIR"

先停止 OpenFic:

Terminal window
docker stop openfic

然后从停止后的容器中复制整个 /data:

Terminal window
docker cp openfic:/data/. "$BACKUP_DIR/data/"

注意这里特意用了:

/data/.

而不是简单 /data/*,这样 .key 这样的隐藏文件也会被复制。

检查备份:

Terminal window
ls -lah "$BACKUP_DIR/data"

必须确认类似:

.key
openfic.db

都存在。

2.8 保留旧容器,不要直接删除,然后启动你的新镜像。#

先把旧容器改名,而不是删除:

Terminal window
OLD_CONTAINER="openfic-old-$(date +%Y%m%d-%H%M%S)"
docker rename openfic "$OLD_CONTAINER"
echo "$OLD_CONTAINER"

例如:

openfic-old-20260915-005230

由于我在第一阶段已经有了 ~/openfic/docker-compose.yml,所以启动新镜像这块儿就不手写 docker run 了,而是直接改 compose 文件用我自己的镜像。

我的原 compose 文件大致类似:

services:
openfic:
image: ghcr.io/syrizelink/openfic:latest
container_name: openfic
restart: unless-stopped
environment:
OPENFIC_DATA_DIR: /data
OPENFIC_AUTH_PASSWORD: "${OPENFIC_AUTH_PASSWORD}"
FORWARDED_ALLOW_IPS: "*"
volumes:
- ./data:/data
networks:
- reverse_proxy
networks:
reverse_proxy:
external: true

我们只需要把原本的 image: ghcr.io/syrizelink/openfic:latest 给改成我们刚刚构建的镜像就可以,比如:

image: openfic-custom:abc1234

改完之后,在正式启动前,先让 compose 帮你检查配置:

Terminal window
cd ~/openfic
sudo docker compose config

主要关注

image:
volumes:
networks:

是否正确。

2.9 如果新版本有问题,怎么快速回滚#

因为我们没有删除旧容器,所以回滚很简单。

先停止新版本:

Terminal window
docker stop openfic

删掉新容器:

Terminal window
docker rm openfic

把旧容器恢复名字:

Terminal window
docker rename "$OLD_CONTAINER" openfic

启动:

Terminal window
docker start openfic

检查:

Terminal window
docker logs --tail 100 openfic

这样就又回到了原来的镜像。

不过这里有一个例外:如果你的新代码包含了数据库 schema migration,并且新版本已经修改了 openfic.db,旧程序未必能兼容新版数据库。

如果你的 fork 没有修改数据库结构,一般风险就较低。

2.10 Google 应用默认凭证配置#

我的 fork 版本修改了原项目的 provider,使其支持 Google Vertex AI. 想要使用 Vertex,需要按照 Google 官方文档配置应用默认凭证,下面就记录我的配置过程。

首先,在服务器上以平时的 ubuntu 用户名运行官方命令:

Terminal window
bash <(curl -sSL https://storage.googleapis.com/cloud-samples-data/adc/setup_adc.sh)

它会帮助安装/运行 gcloud 并设置 ADC. 这里要注意的点就是建议不要用 sudo 执行,否则 ADC 有可能最终落在 root 的 home,而不是我们用户名所在的目录。

完成认证后确认文件存在:

Terminal window
ls -lah ~/.config/gcloud/application_default_credentials.json

然后把我们的 ~/openfic/docker-compose.yml 文件改成类似:

services:
openfic:
image: openfic-custom:124921b
container_name: openfic
restart: unless-stopped
environment:
OPENFIC_DATA_DIR: /data
OPENFIC_AUTH_PASSWORD: "${OPENFIC_AUTH_PASSWORD}"
FORWARDED_ALLOW_IPS: "*"
# 这行
GOOGLE_APPLICATION_CREDENTIALS: /run/secrets/gcp/application_default_credentials.json
volumes:
- ./data:/data
# 还有这行
- /home/ubuntu/.config/gcloud/application_default_credentials.json:/run/secrets/gcp/application_default_credentials.json:ro
networks:
- reverse_proxy
networks:
reverse_proxy:
external: true

接着我们重新创建 OpenFic 容器:

Terminal window
cd ~/openfic
sudo docker compose up -d

检查:

Terminal window
sudo docker ps --filter name=openfic

不要先测试 Vertex,先检查文件:

Terminal window
sudo docker exec openfic sh -lc '
ls -l /run/secrets/gcp/application_default_credentials.json
'

应该能看到类似:

Terminal window
-rw------- ... /run/secrets/gcp/application_default_credentials.json

然后确认环境变量:

Terminal window
sudo docker exec openfic sh -lc '
echo "$GOOGLE_APPLICATION_CREDENTIALS"
'

应该输出:

Terminal window
/run/secrets/gcp/application_default_credentials.json

再做一个存在性测试:

Terminal window
sudo docker exec openfic sh -lc '
test -r "$GOOGLE_APPLICATION_CREDENTIALS" \
&& echo "ADC readable: OK" \
|| echo "ADC readable: FAILED"
'

应该得到:

Terminal window
ADC readable: OK

最后的最后,在浏览器里打开网页,测试 provider 连接与 model 连接是否正常,就大功告成!

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

OpenFic 部署全过程
https://hac.he.cn/posts/others-01/
作者
HAC
发布于
2026-09-15
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
HAC
观之非易,行且克难
Greetings
欢迎来到我的博客!这里主要分享我的学习笔记与兴趣爱好。
音乐
封面

音乐

暂未播放

0:000:00
暂无歌词
分类
标签
站点统计
文章
46
分类
8
标签
19
总字数
116,126
运行时长
0 天
最后活动
0 天前
文章目录