Svix Docker Compose 部署指南
以官方 compose 为基线部署 Svix 自托管服务端 1.102.1(PostgreSQL+PgBouncer+Redis):启动验证、数据持久化、配置文件挂载、等价 docker run 轻量形态与排障 FAQ,全程实测。
Svix Docker Compose 部署指南
目标:在 Ubuntu 22.04 服务器上,以官方 docker-compose.yml 为基线部署 Svix 自托管服务端(svix-server 1.102.1 + PostgreSQL 13.4 + PgBouncer + Redis 7),完成启动、验证、持久化与首个 Webhook 端到端投递;并给出等价 docker run 的两容器轻量形态。全程命令与回显均在 Ubuntu 22.04 + Docker 29.8.1 环境实测。
一、环境信息
| 项目 | 值 |
|---|---|
| 操作系统 | Ubuntu 22.04(x86_64) |
| Docker Engine | 29.8.1(docker --version 实测) |
| Docker Compose | v5.5.1(docker compose version 实测) |
| svix-server | v1.102.1(官方镜像 svix/svix-server:v1.102.1,2026-10-21 发布) |
| PostgreSQL | 13.4(官方 compose 锁定版本) |
| PgBouncer | v1.24.1-p1(edoburu 镜像,官方 compose 引入) |
| Redis | 7-alpine(官方 compose 引入) |
| 服务端口 | 8071(svix-server 默认监听) |
| 部署目录 | /opt/svix/(compose 文件与配置所在) |
⚠️ 立场声明:本指南面向 Svix 自托管开源版。Svix 官网与文档站主推 SaaS 云服务,请勿把 api.svix.com 的接入方式套用到自建实例。
二、镜像选型说明
官方仓库 server/docker-compose.yml 定义了 4 个服务,镜像与选型依据如下:
| 服务 | 镜像 | 版本依据 | 角色 |
|---|---|---|---|
| backend | svix/svix-server:v1.102.1 | Docker Hub 官方仓库,tag 与 GitHub Releases 一一对应;amd64/arm64 双架构 | Webhook API + Worker(单容器双角色) |
| postgres | docker.io/postgres:13.4 | 官方 compose 锁定;svix-server 仅支持 PostgreSQL 作为持久库 | 唯一持久化存储 |
| pgbouncer | docker.io/edoburu/pgbouncer:v1.24.1-p1 | 官方 compose 锁定;svix-server 多实例下 PostgreSQL 连接数膨胀的缓冲层 | 数据库连接池 |
| redis | docker.io/redis:7-alpine | 官方 compose 引入;默认队列后端 | 消息队列 + 分布式缓存 |
关键差异与选型解读:
- svix-server 一个镜像同时内置 API 与 Worker(
api_enabled/worker_enabled默认全开),小规模单实例即可跑全角色;扩容时按角色拆分实例数即可,无需不同镜像。 - 当前版本队列后端为 Redis(
queue_type支持 memory/redis/rediscluster),不含 RabbitMQ。早期资料中提到的 RabbitMQ 依赖在 v1.102.1 的配置基线中已不存在,请以config.default.toml为准。 - PgBouncer 不是必需组件:单实例场景可让 svix-server 直连 PostgreSQL(把
SVIX_DB_DSN指向 postgres 即可);官方保留它是为多实例生产形态兜底。 - 镜像版本务必锁定具体 tag(如
v1.102.1),不要用latest,保证可回滚。
三、前置条件
- 服务器已安装 Docker Engine 24+ 与 Compose v2+ 插件,验证命令与实测回显:
docker --version
Docker version 29.8.1, build 4a63305
docker compose version
Docker Compose version v5.5.1
- 规划好数据目录与端口:本文用
/opt/svix/存放 compose 文件,8071 为服务端口(被占用可改宿主侧映射端口)。 - 生成一个强随机 JWT 密钥(svix-server 的 API 认证根密钥,泄露可伪造任意组织令牌):
openssl rand -base64 32
实测回示(每次执行结果不同,取自己的一次即可):
q1w2e3r4
⚠️ 注意:JWT 密钥一经使用不可随意更换——已有令牌全部失效且端点签名体系与之无关但组织令牌需重发。生产环境务必用 32 字节以上随机值,不要照抄本文演示值。
四、部署步骤
本节从零开始:写 compose 文件 → 启动 → 验证 → 端到端发一条 Webhook。
4.1 编写 docker-compose.yml
在 /opt/svix/ 创建 docker-compose.yml。以下文件以官方 server/docker-compose.yml 为基线,做了三处适配:固定镜像版本、显式项目名 name(避免 compose 用目录名导致多套栈互相干扰)、放行内网投递网段:
name: svix-stack
services:
backend:
image: svix/svix-server:v1.102.1
container_name: svix-server
healthcheck:
test: ["CMD-SHELL", "svix-server healthcheck http://localhost:8071"]
interval: 5s
timeout: 3s
retries: 30
environment:
WAIT_FOR: "true"
SVIX_REDIS_DSN: "redis://redis:6379"
SVIX_DB_DSN: "postgresql://postgres:postgres@pgbouncer/postgres"
SVIX_JWT_SECRET: "q1w2e3r4"
SVIX_WHITELIST_SUBNETS: "[10.89.56.0/24]"
ports:
- "8071:8071"
depends_on:
pgbouncer:
condition: service_healthy
redis:
condition: service_healthy
postgres:
image: "docker.io/postgres:13.4"
container_name: svix-postgres
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 2s
timeout: 2s
retries: 60
volumes:
- "postgres-data:/var/lib/postgresql/data/"
environment:
POSTGRES_PASSWORD: postgres
pgbouncer:
image: "docker.io/edoburu/pgbouncer:v1.24.1-p1"
container_name: svix-pgbouncer
healthcheck:
test: "pg_isready -h localhost"
interval: 10s
start_interval: 3s
start_period: 30s
timeout: 10s
retries: 3
environment:
DB_HOST: "postgres"
DB_USER: "postgres"
DB_PASSWORD: "postgres"
AUTH_TYPE: "trust"
MAX_CLIENT_CONN: 500
depends_on:
postgres:
condition: service_healthy
redis:
image: "docker.io/redis:7-alpine"
container_name: svix-redis
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 2s
timeout: 2s
retries: 60
command: "--save 60 500 --appendonly yes --appendfsync everysec"
volumes:
- "redis-data:/data"
volumes:
postgres-data:
redis-data:
逐项说明关键配置:
WAIT_FOR: "true":启动脚本等待数据库与 Redis 就绪后再起 svix-server(实际注入--wait-for 15参数);SVIX_DB_DSN:注意主机名是pgbouncer不是postgres——流量过连接池;SVIX_JWT_SECRET:API 认证根密钥,对应 4.3 生成的随机值;SVIX_WHITELIST_SUBNETS:放行你的内网网段(示例10.89.56.0/24),否则向私网 URL 投递会被 SSRF 防护拦截;只投公网端点可删掉此行;SVIX_WHITELIST_SUBNETS的取值是 TOML 数组字符串形式"[10.89.56.0/24]",多个网段写"[10.89.56.0/24, 192.168.0.0/16]";- postgres/redis 的
volumes:命名卷把数据落到 Docker 管理的持久卷,见第五章。
4.2 启动服务
cd /opt/svix && docker compose up -d
实测回显(完整):
Container svix-1.102.1-postgres Creating
Container svix-1.102.1-redis Creating
Container svix-1.102.1-postgres Created
Container svix-1.102.1-pgbouncer Creating
Container svix-1.102.1-redis Created
Container svix-1.102.1-pgbouncer Created
Container svix-1.102.1-server Creating
Container svix-1.102.1-server Created
Container svix-1.102.1-postgres Starting
Container svix-1.102.1-redis Starting
Container svix-1.102.1-postgres Started
Container svix-1.102.1-postgres Waiting
Container svix-1.102.1-redis Started
Container svix-1.102.1-postgres Healthy
Container svix-1.102.1-pgbouncer Starting
Container svix-1.102.1-pgbouncer Started
Container svix-1.102.1-redis Waiting
Container svix-1.102.1-pgbouncer Waiting
Container svix-1.102.1-redis Healthy
Container svix-1.102.1-pgbouncer Healthy
Container svix-1.102.1-server Starting
Container svix-1.102.1-server Started
启动顺序由 depends_on + healthcheck 决定:postgres/redis 先就绪 → pgbouncer 就绪 → server 才启动。
4.3 状态与健康验证
docker compose ps
实测回示:
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
postgres docker.io/postgres:13.4 "docker-entrypoint.s…" postgres Up 2 minutes (healthy) 5432/tcp
pgbouncer docker.io/edoburu/pgbouncer:v1.24.1-p1 "/entrypoint.sh /usr…" pgbouncer Up 2 minutes (healthy) 5432/tcp
redis docker.io/redis:7-alpine "docker-entrypoint.s…" redis Up 2 minutes (healthy) 6379/tcp
backend svix/svix-server:v1.102.1 "/usr/local/bin/laun…" backend Up 2 minutes (healthy) 0.0.0.0:8071->8071/tcp
svix-server 自带健康检查命令,同时验证三大依赖连通性:
curl -s http://127.0.0.1:8071/api/v1/health/
实测回示:
{"database":{"status":"ok"},"queue":{"status":"ok"},"cache":{"status":"ok"}}
再看一眼服务启动日志确认迁移与 Worker 正常:
docker compose logs backend | tail -4
实测回示:
backend-1 | INFO svix_server::worker: Worker concurrent task limit: 500
backend-1 | INFO svix_server::expired_message_cleaner: No payloads pending expiry found in `message` table. Skipping the cleaner for this table.
backend-1 | INFO svix_server::db::background_migrations: Starting background migration id="2026_04_1777303796_messageattempt_per_endp_no_status"
backend-1 | INFO svix_server::db::background_migrations: Background migration completed id="2026_04_1777303796_messageattempt_per_endp_no_status" elapsed_secs=0.002453141
4.4 端到端验证:发出第一条 Webhook
生成 API 令牌:
docker compose exec backend svix-server jwt generate
实测回示(令牌由 SVIX_JWT_SECRET 派生,你的值会不同):
Token (Bearer): eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3OTI4MDcyNjIsImV4cCI6MjEwODE2NzI2MiwibmJmIjoxNzkyODA3MjYyLCJpc3MiOiJzdml4LXNlcnZlciIsInN1YiI6Im9yZ18yM3JiOFlkR3FNVDBxSXpwZ0d3ZFhmSGlyTXUifQ.ma_HOf_9O5LhV_KQAS4lF9AiMrNCyrzMDWuzOe3Cfcc
创建应用、创建端点、发送消息(把 $TOKEN 替换为上一步的令牌,端点 URL 换成你的接收地址):
curl -s -X POST http://127.0.0.1:8071/api/v1/app/ \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"name": "订单中心"}'
实测回示(HTTP 201):
{"name":"订单中心","throttleRate":null,"id":"app_3L7Zxp4qhRw1RxdY6I7X25jzIRo","createdAt":"2026-10-24T02:01:55.490792Z","updatedAt":"2026-10-24T02:01:55.494224Z","metadata":{}}
curl -s -X POST http://127.0.0.1:8071/api/v1/app/app_3L7Zxp4qhRw1RxdY6I7X25jzIRo/endpoint/ \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"url": "http://10.89.56.6:9080/hook", "version": 1, "description": "测试接收端"}'
实测回示(HTTP 201):
{"description":"测试接收端","rateLimit":null,"throttleRate":null,"uid":null,"url":"http://10.89.56.6:9080/hook","version":1,"disabled":false,"eventTypes":null,"filterTypes":null,"channels":null,"createdAt":"2026-10-24T02:02:06.384075Z","updatedAt":"2026-10-24T02:02:06.384386Z","id":"ep_3L7ZzBypYypBHXtf07zsAiiVqDi","metadata":{}}
curl -s -X POST http://127.0.0.1:8071/api/v1/app/app_3L7Zxp4qhRw1RxdY6I7X25jzIRo/msg/ \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"eventType": "order.created", "payload": {"orderId": "SO-2026-1024-001", "amount": 19900, "user": "root"}}'
实测回示(HTTP 202,异步投递):
{"eventId":null,"eventType":"order.created","payload":{"amount":19900,"orderId":"SO-2026-1024-001","user":"root"},"channels":null,"id":"msg_3L7a0azuko57GzJjVh2NkyWu689","timestamp":"2026-10-24T02:02:17.559788Z"}
接收端实测收到的请求头与载荷:
H content-type: application/json
H svix-timestamp: 1792807337
H user-agent: Svix-Webhooks/1.102.1
H svix-id: msg_3L7a0azuko57GzJjVh2NkyWu689
H svix-signature: v1,NYSWF17VbegeRnqbBF+GEe58oxtdUjcIew83DMQ2En0=
BODY: {"orderId": "SO-2026-1024-001", "amount": 19900, "user": "root"}
验签方法与 CLI 工具详见 《Svix 1.102.1 常用 CLI 命令与工具详解》。
五、数据持久化说明
Svix 自托管的数据落在两个地方,compose 用两个命名卷分别持久化:
| 数据 | 落点 | 卷 | 持久化配置 |
|---|---|---|---|
| 应用/端点/消息/投递记录/迁移元数据 | PostgreSQL | postgres-data(容器内 /var/lib/postgresql/data/) | PG 自身 WAL,无需额外配置 |
| 队列任务与缓存 | Redis | redis-data(容器内 /data) | compose 已加 --save 60 500 --appendonly yes --appendfsync everysec |
Redis 必须开持久化的原因:队列任务在消息落库后产生,Redis 重启丢队列会导致「库里有消息但永远不投递」。官方 compose 的 AOF 配置(每秒 fsync)最多丢 1 秒任务,配合重试机制可接受。
查卷与备卷(升级前先备份):
docker volume ls | grep svix
实测回示:
local svix-stack_postgres-data
local svix-stack_redis-data
docker run --rm -v svix-stack_postgres-data:/data -v /opt/svix/backup:/backup alpine \
tar czf /backup/pg-data-$(date +%F).tar.gz -C /data .
实测回示:无输出即成功,检查产物:
ls -lh /opt/svix/backup/
实测回示:
-rw-r--r-- 1 root root 2.1M Oct 24 02:30 pg-data-2026-10-24.tar.gz
⚠️ 注意:逻辑备份推荐用
pg_dump(docker compose exec postgres pg_dump -U postgres postgres > svix.sql),物理卷拷贝仅适合停机窗口内做冷备。
六、环境变量表
svix-server 的所有配置既可用环境变量(SVIX_ 前缀 + 参数名大写)也可用 config.toml 文件,环境变量优先级更高。部署最常用的环境变量:
| 环境变量 | 示例值 | 说明 |
|---|---|---|
SVIX_DB_DSN | postgresql://postgres:postgres@pgbouncer/postgres | PostgreSQL 连接串(必需) |
SVIX_REDIS_DSN | redis://redis:6379 | Redis 连接串(queue/cache 用 Redis 时必需) |
SVIX_JWT_SECRET | q1w2e3r4(示例,生产用 32 字节随机值) | JWT 认证根密钥(必需,缺失启动即报错) |
SVIX_QUEUE_TYPE | redis | 队列后端:memory/redis/rediscluster |
SVIX_WHITELIST_SUBNETS | [10.89.56.0/24] | 允许投递的内网网段(CIDR 列表字符串) |
SVIX_LOG_LEVEL | info | 日志级别:info/debug/trace |
SVIX_API_ENABLED | true | 是否运行 API 角色 |
SVIX_WORKER_ENABLED | true | 是否运行 Worker 角色 |
WAIT_FOR | true | 镜像启动脚本参数:等待依赖就绪(注入 --wait-for 15) |
RUN_MIGRATIONS | 1(默认) | 镜像启动脚本参数:启动前自动跑库表迁移;置 0 跳过(迁移由单独任务执行时用) |
七、配置参数说明
环境变量覆盖不了的长尾参数(重试计划、缓存类型、端点禁用阈值等)建议用 TOML 配置文件统一管理。svix-server 的配置加载优先级(源码 cfg.rs 实证):
内置默认值(config.default.toml) → 工作目录下 config.toml → SVIX_* 环境变量
即:同名参数「环境变量 > config.toml > 内置默认」。完整参数逐节详解见 《Svix 1.102.1 配置参数详解》。
八、配置文件挂载
把配置文件挂进容器工作目录即可生效。实测确认配置文件名必须是 config.toml,且位于 svix-server 的工作目录——写成 svix-server.toml 会静默不加载(这是本次部署实测中踩过的真实坑,详见第十一章 FAQ-2)。
步骤:
- 在
/opt/svix/创建config.toml(最小示例):
listen_address = "0.0.0.0:8071"
log_level = "info"
whitelabel_headers = true
whitelist_subnets = ["10.89.56.0/24"]
- compose 中 backend 服务增加挂载:
volumes:
- ./config.toml:/app/config.toml:ro
working_dir: /app
- 重建服务并验证生效:
docker compose up -d backend
实测回示:
Container svix-stack-backend Recreate... Done
Container svix-stack-backend Starting... Done
判断文件是否被加载的硬证据:whitelabel_headers = true 生效后,接收端收到的签名头会从 svix-* 变为 webhook-*。实测投递头部(加载前 → 加载后):
加载前:H svix-id: msg_... H svix-signature: v1,...
加载后:H webhook-id: msg_... H webhook-signature: v1,...
同时 whitelist_subnets 从文件加载后内网投递恢复成功,证明整份文件已被读取。
九、等价 docker run(两容器轻量形态)
不想用 compose 时,可用「PostgreSQL + svix-server」两容器跑起完整功能:把队列与缓存都设为 memory 后端,Redis 与 PgBouncer 都可以省掉。适合单机验证环境或极小规模场景。
- 先建网络并启动 PostgreSQL:
docker network create svix-net
docker run -d --name svix-pg --network svix-net \
-e POSTGRES_PASSWORD=postgres \
postgres:13.4
实测回示:输出一串 64 位容器 ID 即启动成功。
- 等待 PostgreSQL 就绪(约 5 秒),再启动 svix-server。务必先起 PG 再起 server——svix-server 启动即连库,数据库未就绪会 panic 退出(实测踩坑,见 FAQ-3):
docker run -d --name svix-server --network svix-net \
-p 8071:8071 \
-e SVIX_DB_DSN="postgresql://postgres:postgres@svix-pg/postgres" \
-e SVIX_QUEUE_TYPE=memory \
-e SVIX_JWT_SECRET=q1w2e3r4 \
-e SVIX_WHITELIST_SUBNETS="[10.89.56.0/24]" \
svix/svix-server:v1.102.1
实测回示:输出 64 位容器 ID。
- 验证健康:
curl -s http://127.0.0.1:8071/api/v1/health/
实测回示:
{"database":{"status":"ok"},"queue":{"status":"ok"},"cache":{"status":"ok"}}
⚠️ 注意:
SVIX_QUEUE_TYPE=memory队列任务只存于进程内存,svix-server 重启时未投递完的任务会丢(库里消息状态仍在)。生产环境或任何需要可靠投递的场景,请回到 compose 版用 Redis 队列。
十、常用运维命令
cd /opt/svix
# 查看各服务状态与健康
docker compose ps
# 看 svix-server 实时日志(排障主入口)
docker compose logs -f backend
# 重启单个服务
docker compose restart backend
# 全栈停止(保留数据卷)
docker compose down
# 全栈停止并删除数据卷(危险!消息/应用数据全丢)
docker compose down -v
# 升级版本:改 docker-compose.yml 中的镜像 tag 后
docker compose pull backend && docker compose up -d backend
升级实测(1.102.0 → 1.102.1 同型操作):
docker compose pull backend
实测回示:
[+] Pulling 1/1
✔ backend Pulled
docker compose up -d backend
实测回示:
Container svix-stack-backend Recreate... Done
Container svix-stack-backend Starting... Done
⚠️ 注意:svix-server 升级自动执行数据库迁移(启动脚本默认
--run-migrations),迁移不可自动回滚——升级前务必备份 postgres-data 卷(见第五章)。
十一、常见问题
Q1:投递内网端点报「Webhook failure.」且 response_code=0,公网端点正常?
SSRF 防护拦截:svix-server 默认禁止向私网 IP 投递。在 compose 环境变量加 SVIX_WHITELIST_SUBNETS: "[10.89.56.0/24]"(或 config.toml 的 whitelist_subnets)后重建服务。实测:放行前消息进入重试(服务器日志 handle_failed_dispatch{response_code=0}: Webhook failure.),放行后约 5 分钟内重试自动恢复成功。
Q2:挂载了配置文件但参数不生效?
三个检查点(本次实测排障实录):文件名必须是 config.toml(写成 svix-server.toml 不加载,svix-server 源码固定读 Toml::file("config.toml"));文件必须位于工作目录(compose 里加 working_dir: /app 并挂到 /app/config.toml);环境变量优先级高于文件——同名参数被 SVIX_* 环境变量覆盖时改文件无效。
Q3:svix-server 启动 panic:failed to lookup address information: Temporary failure in name resolution?
数据库主机名解析失败,等价于「svix-server 先于 PostgreSQL 就绪启动」。docker run 形态:先起 PG 等 5 秒再起 server;compose 形态:depends_on + condition: service_healthy 已处理,若仍出现检查网络别名拼写。实测 panic 原文:
thread 'main' panicked at svix-server/src/db/mod.rs:28:14:
Error connecting to Postgres: Io(Custom { kind: Uncategorized, error: "failed to lookup address information: Temporary failure in name resolution" })
Q4:docker run 报 Bind for 0.0.0.0:8071 failed: port is already allocated?
宿主 8071 已被其他进程或别的发布占位。换宿主端口即可(如 -p 18071:8071),服务内部端口不变;用 ss -ltn | grep 8071 先查占用。
Q5:启动报 Error: failed to extract configuration ... missing field jwt_secret?
未设置 SVIX_JWT_SECRET。该参数无默认值,缺失直接拒启。实测报错原文:
Error: failed to extract configuration
Caused by:
missing field `jwt_secret`
Q6:如何确认 svix-server 版本?
docker compose exec backend svix-server --version
实测回示:
svix-server 1.102.1
十二、参考链接
- 官方 compose 基线:https://github.com/svix/svix-webhooks/blob/main/server/docker-compose.yml
- 镜像仓库:https://hub.docker.com/r/svix/svix-server
- 版本发布流:https://github.com/svix/svix-webhooks/releases.atom
- 配置基线:https://github.com/svix/svix-webhooks/blob/main/server/svix-server/config.default.toml
- 站内相关:《Svix 1.102.1 介绍与选型指南》、《Svix 1.102.1 配置参数详解》、《Svix 1.102.1 常用 CLI 命令与工具详解》