自托管 Relayium:运行属于你自己的文件与文本传输服务器
最近更新: 2026-08-06
Relayium 采用 AGPL-3.0 许可、开源,服务端是一个自包含的镜像——不需要外部数据库,不需要第三方存储桶,也没有什么需要注册。如果你更想自己运行整套系统而不是依赖 relayium.com,本指南会带你用 Docker 起一台服务器,并让 CLI 指向它。
自托管让你完全掌控数据存放的位置、拥有自己的域名和 TLS 证书,也不依赖别人的运维决策。以下内容全部基于仓库中实际存在的文件——docker-compose.yml、server/.env.example 和 docs/self-hosting.md——因此这里不会出现任何实际不存在的参数或配置项。
为什么要自托管
Relayium 的实时传输采用端到端加密。自托管的 TURN 中继可能承载密文字节,服务器也会处理信令元数据,但两者都无法读取或解密文件明文;服务器和中继均不保存实时内容的服务端副本或历史。服务器确实保存着你的账号,以及——对于存储型/链接型传输——密文数据块和一个小型 SQLite 数据库。自托管意味着这些数据存放在你自己掌控的基础设施上,挂在你自己的域名下,不受其他任何人的运维决策影响。
由于该项目采用 AGPL-3.0 许可并且开源(github.com/relayium/relayium),你可以在信任它之前先读清楚服务端到底做了什么,也可以自由 fork 或修改它。
用 Docker 快速开始
开始之前你需要什么
- 一台装了 Docker Engine 和 Compose 插件的主机。docker compose version 会打印版本号;如果提示 “docker: 'compose' is not a docker command”,说明插件没装。
- 一份仓库克隆。compose 文件是从这份源码构建镜像的,因此它需要旁边的 Dockerfile 和 web/ 目录——没有现成镜像可以直接拉取。
- 给具名卷 relayium-data 准备的磁盘空间:SQLite 数据库,加上你保留的存储型传输密文。
- 如果除你之外还有别人要用,就需要一个域名和一层负责终止 TLS 的反向代理。容器只讲明文 HTTP,而且默认只发布在回环接口上。
- 除此之外别无所需。不需要外部数据库,不需要对象存储桶,也不需要任何第三方账号。
仓库根目录提供了一个 Dockerfile 和一个 docker-compose.yml,它们会构建出一个自包含的镜像——一个静态 Go 二进制文件,同时提供预构建好的 Web 应用,因此仅仅为了运行它,你不需要单独的 Node、Go 工具链或 nginx。
克隆仓库并进入目录。
git clone https://github.com/relayium/relayium.gitcd relayium构建并启动。即使中继是关着的,这个占位密钥也不能省:Compose 在解析文件时就会校验被 profile 关掉的 coturn 服务所需的变量,所以直接跑 docker compose up 会被拒绝。
RELAYIUM_TURN_SECRET=placeholder docker compose up -d --build确认容器是真的起来了,而不是在反复崩溃重启。
docker compose ps问这个实例它到底能不能提供服务。用 /readyz,不要用 /healthz——这两者的区别正是这一步的意义所在,下面的预期结果框里说明了原因。
curl -s http://127.0.0.1:8080/readyz复制配置模板并设置你的公开地址。RELAYIUM_BASE_URL 决定了外发邮件里链接的样子,也决定了会话 Cookie 是否带 Secure 标志,所以它必须是你真实的 https:// 地址。
cp server/.env.example server/.envchmod 600 server/.env在前面放一层 nginx 或 Caddy,为你的域名终止 TLS,并把所有路径——/、/api、/ws、/admin——反代到 8080 端口。然后重启,让 server/.env 生效。
docker compose up -d
一个正常实例长什么样
容器状态为 Up,两个端点都能应答。真正说明问题的是 ready:/healthz 无条件返回 ok,在任何东西打开之前就会通过,所以一个数据库或数据块目录已经不可用的实例同样能骗过它。/readyz 会去 ping SQLite 数据库和数据块目录,任一出问题就返回 503。
$ docker compose ps
NAME IMAGE STATUS PORTS
relayium-server-1 relayium/relayium:local Up 12 seconds 127.0.0.1:8080->8080/tcp
$ curl -s http://127.0.0.1:8080/healthz
ok
$ curl -s http://127.0.0.1:8080/readyz
ready- 这就是完整的服务器,监听在 :8080。生产环境请在前面加一层 nginx 或 Caddy 来处理 TLS——docs/self-hosting.md 说明了 Docker 部署路径以及需要反代哪些路径;Relayium 自己生产环境用的 nginx 配置并未公开。
- 应用配置来自可选的 server/.env 文件,加上 docker-compose.yml 里的 environment: 块。每个设置项都有对应的 RELAYIUM_* 环境变量——可以先复制 server/.env.example 作为起点。
- 基础部署最关键的四个变量是:RELAYIUM_ADDR(监听地址)、RELAYIUM_STATIC(已构建的 Web 应用路径)、RELAYIUM_DB(SQLite 文件路径),以及 RELAYIUM_BLOB_DIR(存储型链接密文的写入位置)。docker-compose.yml 已经为这四项设好了合理的默认值,并把它们持久化在一个具名卷里。
为跨网络传输加上 TURN 中继
同一网络(局域网)的传输,以及基于 SSH 的 push/pull,不需要任何额外配置就能工作。跨网络的实时传输(两台设备各自处于不同的 NAT 之后)有时需要一个 TURN 中继来建立路径——中继全程只能看到密文,绝不会看到文件内容。
docker-compose.yml 里有一个可选的 relay profile,会在启动主服务器的同时启动 coturn(TURN 服务器)和一个用于中继流量计量的小型 Redis 实例。
这个密钥必须送到两个不同的地方,而送错了是不会报错的。coturn 通过 Compose 的变量插值拿到它,插值只从 shell 或项目根目录的 .env 解析。服务端则是从它自己的环境变量里读——也就是 server/.env——而密钥为空就等于彻底关闭 TURN。只设其中一处,你就会得到一个正在运行、服务端却从不为它签发凭据的 coturn:每个容器都报告健康,日志里什么都没有,跨严格 NAT 的传输和加中继之前一样失败。
生成一个足够长的随机密钥。下面所有地方都用同一个值。
openssl rand -hex 32把这个密钥,以及你的域名解析到的中继地址,写进 server/.env,服务端才会真正启用 TURN。
RELAYIUM_TURN_SECRET=<the value from step 1> RELAYIUM_TURN_URLS=turn:example.com:3478,turns:example.com:5349把同一个文件源进 shell,好让 Compose 的插值把完全相同的密钥交给 coturn。用 source 的方式既保持了单一来源,也让密钥不会出现在命令行上被 ps 看见。
set -a; . ./server/.env; set +a带上 relay profile 启动整套服务。
docker compose --profile relay up -d --build在主机防火墙上放行中继端口。coturn 用的是 host 网络模式,所以这些是主机规则而不是 Docker 规则:UDP 3478 与 49152-65535,TCP 3478 与 5349。
确认拿到密钥的是服务端,而不只是 coturn。这条检查专门用来抓那个不出声的失败。
docker compose exec server env | grep RELAYIUM_TURN
一个正常的中继长什么样
从服务端容器内部查,两个变量都非空。coturn 起来了本身说明不了任何事——浏览器拿到的中继凭据只可能由服务端签发。
$ docker compose exec server env | grep RELAYIUM_TURN
RELAYIUM_TURN_SECRET=3f7a…
RELAYIUM_TURN_URLS=turn:example.com:3478,turns:example.com:5349- coturn 需要主机真实的公网 IP,以及一段开放的 UDP 端口范围才能工作——docs/self-hosting.md 说明了通过 Docker 的 relay profile 运行它;Relayium 自己生产环境用的 coturn 配置(以及安装脚本)并未公开。
- 如果不加 --profile relay 和 RELAYIUM_TURN_SECRET,服务器依然能正常运行——只是跨网络传输会退化为纯 STUN,这对较宽松的 NAT 类型没问题,但对最严格的类型不行。
在你自己的机器上装 CLI
最后这一步是在你自己的电脑上运行 relayium CLI(不是服务器),所以没装的话先在本机装上。macOS 或 Linux:
curl -fsSL https://relayium.com/install.sh | sh
- relayium.com/cli 列出了所有安装方式——Windows 二进制、发布页,或装了 Go 用 go build。
- relayium --version 可确认。没装 CLI,下面的命令只会报 “command not found”。
让 CLI 指向你的服务器
Relayium CLI 默认使用 relayium.com 的会合服务器来完成跨网络的 send/receive 与 text。传入 --server 即可改用你自己的服务器。
在上一节已经装好 CLI 的机器上,登录到你自己的服务器而不是 relayium.com。它会打印一个网址和一个码;在已登录你实例的浏览器里批准它。
relayium login --server https://your-domain确认已保存的凭据绑定的是哪台服务器。whoami 不接受任何参数——它汇报的是登录实际写下的内容,这正是它值得一跑的原因。
relayium whoami发送时带上同一个 --server。不带的话,CLI 会在 relayium.com 上生成配对码,另一端在你的实例上永远找不到它。
relayium send ./report.pdf --server https://your-domain在另一台机器上,用打印出来的配对码和同一个 --server 接收。文本会话的用法完全一样。
relayium receive 483920 --server https://your-domainrelayium text --server https://your-domainrelayium text 483920 --server https://your-domain
怎么确认它连的是你自己的实例
whoami 会打印账号,后面括号里跟着它绑定的服务器。那里出现的是你自己的域名而不是 relayium.com,就是确认。
$ relayium login --server https://your-domain
Open https://your-domain/device and enter code: WDJB-MJHT
logged in as you@example.com
$ relayium whoami
you@example.com (https://your-domain)- 无论用哪个服务器,CLI 都是免费的——--server 只是改变了配对码握手所连接的会合服务器。send 或 text 不带码运行时会在该服务器生成配对码,云端 up 也存到那台服务器上的账号下,所以要先用 relayium login --server https://your-domain 在那边登录;receive、down,以及使用打印配对码加入的 text 都无需登录。
- text 两端必须同时在线。消息使用独立的端到端加密 P2P 直连会话;CLI text 只支持直连,不使用网页版的 TURN 中继。Relayium 和你的自托管服务器都不保存消息正文或服务端历史,但任一终端或接收方都可以在收到文本后复制或留存。
- push/pull(走你自己的 SSH)以及 serve + daemon 直连的 push relayium://host,无论是否自托管都完全不会接触 relayium.com——它们直接连到你指定的远程地址。
出问题时怎么办
几乎所有失败的自托管都逃不出这五种。每一种都有一行可以读的输出或一条可以跑的命令来判定,而其中三种在你跑那条检查之前,看起来都像是成功了。
现象、检查、修复
- docker compose up 直接拒绝启动,什么都还没开始构建。
docker compose up -d --build # required variable RELAYIUM_TURN_SECRET is missing a valueCompose 会先对整个文件做插值,然后才按 profile 过滤,所以被关掉的 coturn 服务所需的变量照样会被校验,即便中继根本没启用。前面加任意占位值即可——RELAYIUM_TURN_SECRET=placeholder docker compose up -d --build——等你真正启用 relay profile 时再把它换成真正的密钥。
- 容器状态是 Up,但另一台机器上的浏览器访问不到。
docker compose ps # PORTS 127.0.0.1:8080->8080/tcp绑定到回环是默认行为,这样公网主机才不会把明文 HTTP 直接暴露到互联网上。生产环境请保持这个默认,并在同一台主机上用反向代理终止 TLS。如果是没有反代的纯局域网机器,用 RELAYIUM_BIND=0.0.0.0 docker compose up -d 把它发布得更宽——这个变量由 compose 读取,服务端本身并不认识它。
- /healthz 返回 ok,但注册失败,存储型链接也一直出不来。
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080/readyz # 503/healthz 无条件返回 ok,只能证明进程在监听。/readyz 会去 ping SQLite 数据库和数据块目录,所以 503 意味着其中之一不可用——检查 relayium-data 卷是否挂上了,以及 RELAYIUM_DB 和 RELAYIUM_BLOB_DIR 是否指向卷里面。
- relayium login 打印出的验证网址在 localhost 上,你根本打不开。
relayium login --server https://your-domain # Open http://localhost:8080/device and enter code: WDJB-MJHT那个网址是服务端用 RELAYIUM_BASE_URL 拼出来的,而它的默认值就是 http://localhost:8080。在 server/.env 里把它设成你真实的 https:// 地址再重启。它同时决定会话 Cookie 是否带 Secure 标志,所以设错了不只是难看的问题。
- coturn 在跑,但跨严格 NAT 的传输依然失败——而且日志里什么都没有。
docker compose exec server env | grep RELAYIUM_TURN # 没有任何输出密钥通过 Compose 插值到了 coturn,却从来没到过服务端,而服务端密钥为空就等于彻底关闭 TURN。把 RELAYIUM_TURN_SECRET 和 RELAYIUM_TURN_URLS 写进 server/.env,用 set -a; . ./server/.env; set +a 源进 shell 让插值看到同一个值,然后重启 relay profile。那条检查必须两个变量都非空才算过。
常见问题
我需要搭建 TURN 吗?
只有当你希望跨网络的实时传输能穿透严格 NAT 时才需要。同一网络传输、基于 SSH 的 push/pull,以及 daemon 直连都不需要它——TURN 只用于跨网络配对码路径上的 NAT 穿透。
自托管之后 CLI 还免费吗?
是的。无论连接的是 relayium.com 还是你自己运行的服务器,CLI 都完全免费——--server 只是让它指向你的实例。send 或 text 不带码生成配对码时,以及 up 存文件时,需要目标服务器上的账号;receive、down 和使用打印配对码加入的 text 都无需登录。
我可以用自己的域名和 TLS 证书吗?
可以。Docker 镜像本身在 :8080 上以明文 HTTP 监听;在前面加一层 nginx 或 Caddy,配上你自己的域名和证书(例如通过 certbot/Let's Encrypt)。docs/self-hosting.md 说明了需要反代哪些路径;Relayium 自己生产环境用的 nginx 配置并未公开,需要你自己编写。
我自托管的服务器会存储哪些数据?
一个位于 RELAYIUM_DB 的 SQLite 数据库(账号、会话),以及——对于存储型/链接型传输——位于 RELAYIUM_BLOB_DIR 的加密数据块,服务器自己也无法解密它们。服务器不保存实时文件或消息正文,只转发信令握手;接收端仍可保存文件或留存文本。
安装免费的 Relayium CLI,用 --server 指向你自己的服务器。
获取 CLI