OpenFic 部署全过程
关于 OpenFic 的简介,请访问原项目链接。本篇仅记录我在 VPS 上部署自托管 OpenFic 的全过程。
我的部署流程大致分为两个阶段:
- 第一阶段:直接部署官方 repo
- 第二阶段:部署自己 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.4fiction.example.com → 1.2.3.4然后我们可以检查:
getent ahostsv4 fiction.example.com这条指令应该返回我们的 VPS 公网 IP。
而关于公网端口的开放,下列端口
2280443已经足够。
1.1.2 建立 Caddy 和 OpenFic 的共享 Docker 网络
执行:
sudo docker network create reverse_proxy确认:
sudo docker network ls我们应该看到:
reverse_proxy1.2 修改现有 Docmost Compose
由于我先前已经部署了 Docmost,所以我先进入:
cd ~/docmost编辑:
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检查:
sudo docker compose config -q没有输出就说明 YAML 基本正常。
1.3 让现有 Caddy 加入新网络
应用修改:
sudo docker compose up -d caddy检查:
sudo docker compose ps应该仍然:
caddy Updocmost Updb Upredis Up再确认 Docmost 没受影响:
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/接着我们执行:
openssl rand -base64 36这一步生成的就是一个随机的强密码,当然你也可以用自己的,我们复制输出结果。
然后:
vi ~/openfic/.env填写:
OPENFIC_AUTH_PASSWORD=这里换成刚生成的密码例如:
OPENFIC_AUTH_PASSWORD=VJKy2pFTZ7A9VdgJmF9r2CVg8SLwKUZmP9C8X7fhdnL7F9hJ保存后:
chmod 600 ~/openfic/.env如果不配置这个变量,应用级密码保护就是关闭状态。也就是说,任何人访问你的域名都能进入你的系统。
1.5 创建 OpenFic Compose
执行:
cd ~/openficvi 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 设置成其持久化数据目录。
检查:
sudo docker compose config -q1.6 启动 OpenFic 并验证 Caddy 能不能找到 OpenFic
首先:
cd ~/openficsudo docker compose pull然后:
sudo docker compose up -d检查:
sudo docker compose ps应该类似:
NAME IMAGE STATUSopenfic ghcr.io/syrizelink/openfic:latest Up查看日志:
sudo docker compose logs --tail=100 openfic如果想实时查看:
sudo docker compose logs -f openfic退出日志查看:
Ctrl+C因为现在 OpenFic没有映射宿主机端口,所以我们不能再:
curl 127.0.0.1:8000可以直接从 Caddy 容器测试 Docker 网络:
cd ~/docmostsudo docker compose exec caddy \ wget -qO- http://openfic:8000/api/v1/health正常的话应该看到类似:
{"status":"healthy","version":"..."}如果成功,说明:
Caddy → OpenFic这条链路已经打通。
1.7 修改现有的 Caddyfile
现在回到:
cd ~/docmostvi Caddyfile我当时应该类似:
story.example.com { reverse_proxy docmost:3000}这是我原来的部署方式。
然后我将其改成:
story.example.com { reverse_proxy docmost:3000}
fiction.example.com { reverse_proxy openfic:8000}将两个域名替换成你自己的真实域名。
1.8 检查并重新加载 Caddy
先验证:
cd ~/docmost
sudo docker compose exec caddy \ caddy validate \ --config /etc/caddy/Caddyfile如果看到类似:
Valid configuration重新加载:
sudo docker compose exec caddy \ caddy reload \ --config /etc/caddy/Caddyfile然后我们就可以访问 OpenFic,用浏览器打开:
https://fiction.example.comCaddy 会负责:
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如果遇到了这个问题,可以按下面步骤排查:
# 1. 原来的 Docmost 是否正常curl -I https://story.你的域名
# 2. 新域名解析到了哪里getent ahostsv4 fiction.你的域名getent ahostsv6 fiction.你的域名
# 3. 查看实际 Caddyfilecd ~/docmostcat 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 为例,我执行
cd ~/docmost
sudo docker compose exec caddy \ cat /etc/caddy/Caddyfile然后就发现只有
story.example.com { reverse_proxy docmost:3000}这里我们就需要重新创建 Caddy 容器:
cd ~/docmost
sudo docker compose up -d --force-recreate caddy这样,新的容器就会重新执行:
宿主机 ./Caddyfile ↓bind mount ↓/etc/caddy/Caddyfile1.9 以后两个项目的管理方式
这是采用两个 Compose 项目最大的好处。
以我的 VPS 为例,如果要管理 Docmost:
cd ~/docmost
sudo docker compose pssudo docker compose logs -fsudo docker compose restart docmost管理 OpenFic:
cd ~/openfic
sudo docker compose pssudo docker compose logs -fsudo docker compose restart openfic更新 OpenFic:
cd ~/openfic
sudo docker compose pullsudo docker compose up -d这样,两边就互不影响。唯一共用的组件是就 Caddy,因此以后不用在 ~/openfic 中再创建 Caddy 容器。
2. Fork 版本部署
2.1 记录已有配置
我们先找到当前 OpenFic 容器,并记录现有配置。查看所有运行中的容器:
docker ps或者只显示比较有用的信息:
docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}'我们应该能看到类似:
NAMES IMAGE STATUS PORTSopenfic ghcr.io/syrizelink/openfic:latest Up 3 days 0.0.0.0:8000->8000/tcp下面假设容器名就是:
openfic如果你的实际容器名不同,把后面命令里的 openfic 替换掉。
先记录当前镜像:
docker inspect openfic --format 'Image={{.Config.Image}}'再记录端口:
docker port openfic再查看 restart policy:
docker inspect openfic --format 'RestartPolicy={{.HostConfig.RestartPolicy.Name}}'最后查看环境变量:
docker inspect openfic --format '{{range .Config.Env}}{{println .}}{{end}}'2.2 确认 data 有没有持久化挂载。
如果是按照我的第一阶段部署方式,那么数据是持久化的。也就是说,以后更新版本,重新创建容器不会影响服务器端保存的数据。
不过我们还是可以检查一下,运行:
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:
docker inspect openfic --format '{{range .Mounts}}{{if eq .Destination "/data"}}Type={{.Type}} Name={{.Name}} Source={{.Source}}{{end}}{{end}}'如果按照官方 README 的命令:
docker run -d -p 8000:8000 -v "openfic:/data" --name openfic ghcr.io/syrizelink/openfic:latest部署,那么正常应该得到:
Type=volume Name=openfic ...2.3 检查 /data 内的数据是否真的存在。
运行:
docker exec openfic sh -lc 'ls -lah /data'正常情况下应该能看到:
.keyopenfic.dbcheckpoints.db...也可以专门检查两个最关键文件:
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:
git clone https://github.com/YOUR_USERNAME/OpenFic.git OpenFic-custom然后:
cd OpenFic-custom检查当前分支:
git branch --show-current检查最后一次 commit:
git log -1 --oneline检查工作目录:
git status理想状态:
nothing to commit, working tree clean如果你的代码以后已经 clone 过了,就不需要重新 clone:
cd ~/OpenFic-customgit pull --ff-only可以把 Docker 镜像 tag 直接和 Git commit 绑定:
TAG=$(git rev-parse --short HEAD)echo "$TAG"比如输出:
8f47e21那么你的镜像就叫:
openfic-custom:8f47e21这样将来出了问题,我们就可以知道服务器跑的到底是哪版源码。
2.5 在旧 OpenFic 继续运行的情况下,构建新镜像。
这一步先不碰旧容器,运行:
docker build -t openfic-custom:$TAG .想看更完整日志可以:
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,而没有新增依赖,通常不会遇到这个问题。
构建完成后检查:
docker images | grep openfic-custom应该出现:
openfic-custom 8f47e21 ...2.6 先用空数据启动一次新镜像做 smoke test
先启动临时测试容器:
docker run -d \ --name openfic-smoke \ -p 18000:8000 \ openfic-custom:$TAG检查:
docker ps | grep openfic-smoke查看日志:
docker logs --tail 100 openfic-smoke测试 HTTP:
curl -I http://127.0.0.1:18000/或者:
curl -fsS http://127.0.0.1:18000/ >/dev/null && echo "OpenFic smoke test OK"如果输出:
OpenFic smoke test OK至少说明我们的镜像能正常启动。
然后删除测试容器:
docker stop openfic-smokedocker rm openfic-smoke2.7 正式切换前,停旧容器并完整备份 /data
这一步可选,如果还没存什么重要数据,或者就自己一个人用,没有多用户的话,也可以直接跳。
创建备份目录:
BACKUP_DIR="$HOME/openfic-backup-$(date +%Y%m%d-%H%M%S)"mkdir -p "$BACKUP_DIR/data"echo "$BACKUP_DIR"先停止 OpenFic:
docker stop openfic然后从停止后的容器中复制整个 /data:
docker cp openfic:/data/. "$BACKUP_DIR/data/"注意这里特意用了:
/data/.而不是简单 /data/*,这样 .key 这样的隐藏文件也会被复制。
检查备份:
ls -lah "$BACKUP_DIR/data"必须确认类似:
.keyopenfic.db都存在。
2.8 保留旧容器,不要直接删除,然后启动你的新镜像。
先把旧容器改名,而不是删除:
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 帮你检查配置:
cd ~/openficsudo docker compose config主要关注
image:volumes:networks:是否正确。
2.9 如果新版本有问题,怎么快速回滚
因为我们没有删除旧容器,所以回滚很简单。
先停止新版本:
docker stop openfic删掉新容器:
docker rm openfic把旧容器恢复名字:
docker rename "$OLD_CONTAINER" openfic启动:
docker start openfic检查:
docker logs --tail 100 openfic这样就又回到了原来的镜像。
不过这里有一个例外:如果你的新代码包含了数据库 schema migration,并且新版本已经修改了 openfic.db,旧程序未必能兼容新版数据库。
如果你的 fork 没有修改数据库结构,一般风险就较低。
2.10 Google 应用默认凭证配置
我的 fork 版本修改了原项目的 provider,使其支持 Google Vertex AI. 想要使用 Vertex,需要按照 Google 官方文档配置应用默认凭证,下面就记录我的配置过程。
首先,在服务器上以平时的 ubuntu 用户名运行官方命令:
bash <(curl -sSL https://storage.googleapis.com/cloud-samples-data/adc/setup_adc.sh)它会帮助安装/运行 gcloud 并设置 ADC. 这里要注意的点就是建议不要用 sudo 执行,否则 ADC 有可能最终落在 root 的 home,而不是我们用户名所在的目录。
完成认证后确认文件存在:
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 容器:
cd ~/openficsudo docker compose up -d检查:
sudo docker ps --filter name=openfic不要先测试 Vertex,先检查文件:
sudo docker exec openfic sh -lc 'ls -l /run/secrets/gcp/application_default_credentials.json'应该能看到类似:
-rw------- ... /run/secrets/gcp/application_default_credentials.json然后确认环境变量:
sudo docker exec openfic sh -lc 'echo "$GOOGLE_APPLICATION_CREDENTIALS"'应该输出:
/run/secrets/gcp/application_default_credentials.json再做一个存在性测试:
sudo docker exec openfic sh -lc 'test -r "$GOOGLE_APPLICATION_CREDENTIALS" \ && echo "ADC readable: OK" \ || echo "ADC readable: FAILED"'应该得到:
ADC readable: OK最后的最后,在浏览器里打开网页,测试 provider 连接与 model 连接是否正常,就大功告成!
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!





