Specivo 是自托管(私有化部署)的,整套系统都运行在你自己控制的服务器上:应用、数据库、搜索索引、后台工作进程。本文带你在一台小型云主机上从零搭建实例,用自己的域名配好 HTTPS,开启语义搜索,并创建第一个项目、维基(Wiki)页面和工作项(Issue)。做完之后,你会拥有一个可用的实例,还能亲眼看到搜索用页面中从未出现过的词找到了这个页面。
本文的所有操作都在一台全新的 DigitalOcean 云主机上完成(每月 12 美元,1 vCPU、2 GB 内存、Ubuntu 24.04)。任何服务商都可以:Hetzner、Vultr,或者你自己硬件上的虚拟机,只要有一台能以 root 登录、带公网 IP 的 Linux 主机即可。
最终你会得到什么
- 运行在 Docker 中的 Specivo(应用、PostgreSQL、Redis、nginx 和两个 Celery 工作进程)
- 自己域名上的正式 Let's Encrypt 证书
- 已开启的语义搜索(“按语义”搜索)
- 一个演示项目,包含一个维基页面和一个支持类工作项,并验证搜索能找到它们
示例中使用的域名是 specivo.acmewasterecycling.com,看到它的地方请替换成你自己的域名。
开始之前
你需要准备三样东西:
- 一台服务器。2 GB 内存是切实可行的最低配置。Specivo 在低配机器上也能流畅运行;语义搜索的嵌入模型是最占资源的单个组件,对小团队来说它也能装进 2 GB。
- 一个你控制的域名,用来把子域名指向服务器并签发 TLS 证书。
- 一个 SSH 密钥。云服务商在创建服务器时允许你直接绑定公钥,这比密码登录安全得多。本文后续内容假定你通过 SSH 密钥认证以
root身份登录。
第 1 步:创建服务器
在 DigitalOcean(或你选择的服务商)创建一台云主机:
- 镜像:Ubuntu 24.04 LTS
- 套餐:Basic,1 vCPU / 2 GB 内存 / 50 GB 磁盘
- 认证方式:你的 SSH 密钥
记下分配给你的公网 IP。接下来所有操作都通过 SSH 完成:
ssh root@YOUR_SERVER_IP
第 2 步:把域名指向服务器
为子域名添加一条 DNS A 记录,指向云主机的 IP:
specivo.acmewasterecycling.com. A YOUR_SERVER_IP
这一步要尽早做。DNS 生效需要几分钟,而第 7 步申请证书前必须已经能够解析。用下面的命令检查:
dig +short specivo.acmewasterecycling.com
输出的是你云主机的 IP,就说明可以继续了。
第 3 步:安装 Docker
Specivo 以 Docker 容器形式发布,所以主机上只需要安装 Docker 和 Compose 插件。从 Docker 官方软件源安装:
apt-get update
apt-get install -y ca-certificates curl git
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] \
https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" \
> /etc/apt/sources.list.d/docker.list
apt-get update
apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
systemctl enable --now docker
确认安装成功:
docker --version
docker compose version
第 4 步:获取并配置 Specivo
克隆代码仓库。你只需要其中的 Compose 文件和 nginx 配置;应用镜像本身从 Docker Hub 拉取,如果 Docker Hub 被屏蔽,也可以从 GitHub Container Registry 拉取(见下文说明)。
cd /opt
git clone https://github.com/specivo/specivo.git
cd specivo
Specivo 通过两个文件配置:.env 存放非敏感的默认值,.env.local 存放密钥。有一个交互式向导(make configure),但直接写文件同样简单,自动化部署时也正是这么做的。
先生成两个高强度密钥:一个作为应用的 SECRET_KEY,一个作为数据库密码:
python3 -c "import secrets; print(secrets.token_hex(64))" # SECRET_KEY
python3 -c "import secrets; print(secrets.token_hex(16))" # database password
创建 .env(把数据库密码和域名换成你自己的):
COMPOSE_PROJECT_NAME=specivo
SPECIVO_PORT=9933
SPECIVO_DATA_DIR=./specivo-data
SPECIVO_VERSION=latest
DATABASE_URL=postgresql+asyncpg://specivo:YOUR_DB_PASSWORD@db:5432/specivo
POSTGRES_DB=specivo
POSTGRES_USER=specivo
POSTGRES_PASSWORD=YOUR_DB_PASSWORD
REDIS_URL=redis://redis:6379/0
DEBUG=false
REGISTRATION_MODE=invite_only
CAPTCHA_ENABLED=false
CORS_ORIGINS=["https://specivo.acmewasterecycling.com"]
ALLOWED_HOSTS=["specivo.acmewasterecycling.com","localhost","127.0.0.1"]
创建 .env.local,写入刚生成的密钥,并收紧文件权限:
SECRET_KEY=YOUR_GENERATED_SECRET_KEY
chmod 600 .env.local
其中几项配置值得了解一下:
REGISTRATION_MODE=invite_only表示陌生人无法在公网地址上自行注册。其他可选值为open和disabled。ALLOWED_HOSTS和CORS_ORIGINS把实例限定在你的域名上。DEBUG=false时,Host头不匹配的请求会被直接拒绝。- 起步阶段用
SPECIVO_VERSION=latest没问题。但对于要长期依赖的部署,请固定到具体版本(例如0.1.9),让升级由你主动决定,而不是悄悄发生。 - 如果从你的服务器访问 Docker Hub 被屏蔽或很慢,在
.env中加上SPECIVO_IMAGE=ghcr.io/specivo/specivo。它会从 GitHub Container Registry 拉取同一个镜像、同样的标签(Specivo 0.12.0 起支持)。
最后,创建容器用于持久化存储的挂载目录:
mkdir -p specivo-data/{postgresql,redis,attachments,themes,models,logs/nginx,logs/app}
第 5 步:启动服务
docker compose up -d
首次运行会下载镜像,可能需要一分钟左右。API 容器的入口脚本会等待数据库就绪、执行全部迁移,并写入默认的追踪器、状态和优先级,这些都不需要手动操作。观察启动过程:
docker compose ps
当 api 和 db 显示 healthy 时,就有六个容器在运行:nginx、api、db(带 pgvector 的 PostgreSQL)、redis、celery-worker 和 celery-beat。此时应用已在服务器内部的 9933 端口响应,但还没有对外暴露,接下来两步解决这个问题。
第 6 步:创建第一个管理员
第一个账户没有公开注册入口,需要在命令行创建。
不要叫它
admin。自动化登录攻击都从最常见的用户名入手(admin、administrator、root),再配合密码字典。选一个不那么显眼的用户名,攻击者就得同时猜中两个未知量而不是一个,绝大多数顺手扫过来的暴力破解在用户名这一关就失败了。用一个专属于你的名字,例如alex.ops或mwiggins。
生成一个高强度密码并创建账户:
docker compose exec api python -m specivo.cli.admin create \
--login alex.ops --email [email protected] --password 'a-long-random-password'
这个账户可以创建项目、邀请团队其他成员,并在 Web 界面中管理一切。
第 7 步:配置 HTTPS
目前应用在 9933 端口上只提供明文 HTTP。干净的对外暴露方式是在主机上运行 nginx 作反向代理:由它在 80/443 端口终止 TLS,再转发给 Specivo 容器。安装 nginx 和 Certbot:
apt-get install -y nginx certbot python3-certbot-nginx
在 /etc/nginx/sites-available/specivo.acmewasterecycling.com 创建站点配置:
server {
listen 80;
listen [::]:80;
server_name specivo.acmewasterecycling.com;
# Allow large attachment uploads
client_max_body_size 50m;
location / {
proxy_pass http://127.0.0.1:9933;
proxy_http_version 1.1;
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;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 120s;
}
}
启用该配置,删除默认站点,然后重新加载:
ln -sf /etc/nginx/sites-available/specivo.acmewasterecycling.com /etc/nginx/sites-enabled/
rm -f /etc/nginx/sites-enabled/default
nginx -t && systemctl reload nginx
现在申请证书。Certbot 会替你修改 nginx 配置,添加 HTTPS 的 server 块,并设置从 HTTP 的跳转:
certbot --nginx -d specivo.acmewasterecycling.com \
--non-interactive --agree-tos -m [email protected] --redirect
打开 https://specivo.acmewasterecycling.com,应该能看到带有效证书的登录页。Certbot 还会安装一个续期定时器,证书会在过期前自动更新。

用刚创建的管理员账户登录。首先看到的是一个空的仪表盘:

第 8 步:关上后门
还有一处疏漏。Docker 发布容器端口时直接写 iptables 规则,完全绕过 ufw。所以即便开了防火墙,9933 端口仍可能从互联网直接访问,让人能通过明文 HTTP 访问应用,绕开你的证书。
解决办法是把发布的端口只绑定到 localhost。编辑 docker-compose.yml 中 nginx 服务的端口映射:
ports:
- "127.0.0.1:${SPECIVO_PORT:-9933}:80"
重建容器并开启基础防火墙(先放行 SSH,免得把自己锁在外面):
docker compose up -d nginx
ufw allow OpenSSH
ufw allow "Nginx Full"
ufw --force enable
现在 9933 端口只通过回环接口响应主机上的 nginx;公网只能经由 443 端口的 HTTPS 访问 Specivo。
第 9 步:开启语义搜索
默认情况下,搜索按词匹配(PostgreSQL 全文搜索)。语义搜索增加了按语义匹配:像“garbage truck collection days”(垃圾车收运日)这样的查询,可以找到一篇讲“emptying residential bins on a weekly rotation”(每周轮换清空居民垃圾桶)的页面,即使两者没有一个共同的词。它运行在本机模型上,不调用任何外部 API。
下载模型(multilingual-e5-small,MIT 许可,约 400 MB):
cd /opt/specivo
bash scripts/download-model.sh
文件会存放在 specivo-data/models/。重启应用让它加载模型,然后为已有内容生成向量嵌入:
docker compose restart api celery-worker celery-beat
docker compose exec api python -m specivo.cli.backfill_embeddings
你会看到它报告模型加载情况,以及已索引的工作项和维基页面数量。从此以后,新建的内容会在后台自动生成向量嵌入。(在 1 vCPU 的机器上,模型每批处理需要几秒钟,对小团队足够。如果你的实例写入非常频繁,再加一个核。)
第 10 步:创建项目
剩下的在浏览器里完成。进入 项目 → 新建项目,填写名称、短标识符和一个大写的项目标识(它会作为每个工作项编号的前缀,例如 ACME-1)。工作项始终启用;维基和工时记录是可选模块。

创建后,项目会连同其模块一起显示:

第 11 步:编写维基页面
打开项目的 维基,选择 新建页面。编辑器使用 Markdown,带实时预览,slug 根据标题自动生成。

保存后,Specivo 会渲染页面并根据你的标题生成目录。每次保存都会记录版本,可以查看历史并回滚。

第 12 步:创建工作项
在项目的 工作项 中点击 新建工作项。选择追踪器(Bug(缺陷)、Feature(功能)、Task(任务)、Support(支持)),填写主题和 Markdown 格式的描述,再设置优先级。

它会带着自动生成的编号出现在工作项列表中:

工作项详情页是实际工作发生的地方:状态、评论、与其他工作项的关联、附件和工时记录:

第 13 步:验证搜索有效
这是收获时刻。打开 搜索,输入一条描述那篇维基页面但不使用其中任何一个词的查询。试试“garbage truck collection days”。页面里没有提到“garbage”“trucks”或“days”,说的是“emptying residential bins on a weekly rotation”。开启 混合 模式后,它照样排在第一位,那个工作项紧随其后:

把同一条查询切换到 关键词 模式,则一无所获,因为这些词一个都没在内容里出现过:

这种对比正是语义搜索的意义:即使记不清团队当初的确切用词,人们也能找到想要的内容。
回到仪表盘,你的项目、工作项和最近的维基动态现在一目了然:

生产环境检查清单
在往实例里放入真实工作之前:
- 设置唯一的
SECRET_KEY,写在.env.local中(第 4 步已经做过,别把自带的默认值用到线上就行)。 - 固定
SPECIVO_VERSION到具体版本,而不是latest。 - 保持
REGISTRATION_MODE关闭(invite_only或disabled),只要实例在公网上。 - 两样东西一起备份:PostgreSQL 导出文件和
specivo-data/目录(附件、日志、模型)。只恢复其中一个,得到的只是半个系统。 - 留意内存,尤其在 2 GB 的机器上。整套服务加嵌入模型对小团队来说很宽裕,但如果同一主机还跑其他服务,请给它更多余量或配置交换文件。
运行了什么,花费多少
默认安装共六个容器:nginx(入口)、API、带 pgvector 的 PostgreSQL(它同时也是搜索索引,所以不需要运维 Elasticsearch)、Redis(缓存和任务队列),以及两个负责后台任务(如向量嵌入和通知)的 Celery 进程。在本文使用的 2 GB 云主机上,开启语义搜索后整套服务的内存占用依然宽裕。你不需要另外搭建托管数据库、外部搜索服务或消息队列。就是一台主机,加上面的这些容器。
这就是全部配置:一台服务器、一个域名、Docker 和一张证书,而每一个字节都属于你。
保持更新
Specivo 的新版本以新的 specivo/specivo 镜像发布到 Docker Hub,同时以 ghcr.io/specivo/specivo 发布到 GitHub Container Registry。升级是有计划且快速的:备份、固定新版本、拉取、重启,容器会在启动时应用新的迁移。准备升级时,请参考如何升级自托管的 Specivo 实例。