Svix Docker Compose 部署指南

以官方 compose 为基线部署 Svix 自托管服务端 1.102.1(PostgreSQL+PgBouncer+Redis):启动验证、数据持久化、配置文件挂载、等价 docker run 轻量形态与排障 FAQ,全程实测。

SvixSvixWebhook中间件2026/10/24

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 Engine29.8.1(docker --version 实测)
Docker Composev5.5.1(docker compose version 实测)
svix-serverv1.102.1(官方镜像 svix/svix-server:v1.102.1,2026-10-21 发布)
PostgreSQL13.4(官方 compose 锁定版本)
PgBouncerv1.24.1-p1(edoburu 镜像,官方 compose 引入)
Redis7-alpine(官方 compose 引入)
服务端口8071(svix-server 默认监听)
部署目录/opt/svix/(compose 文件与配置所在)

⚠️ 立场声明:本指南面向 Svix 自托管开源版。Svix 官网与文档站主推 SaaS 云服务,请勿把 api.svix.com 的接入方式套用到自建实例。

二、镜像选型说明

官方仓库 server/docker-compose.yml 定义了 4 个服务,镜像与选型依据如下:

服务镜像版本依据角色
backendsvix/svix-server:v1.102.1Docker Hub 官方仓库,tag 与 GitHub Releases 一一对应;amd64/arm64 双架构Webhook API + Worker(单容器双角色)
postgresdocker.io/postgres:13.4官方 compose 锁定;svix-server 仅支持 PostgreSQL 作为持久库唯一持久化存储
pgbouncerdocker.io/edoburu/pgbouncer:v1.24.1-p1官方 compose 锁定;svix-server 多实例下 PostgreSQL 连接数膨胀的缓冲层数据库连接池
redisdocker.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,保证可回滚。

三、前置条件

  1. 服务器已安装 Docker Engine 24+ 与 Compose v2+ 插件,验证命令与实测回显:
docker --version
Docker version 29.8.1, build 4a63305
docker compose version
Docker Compose version v5.5.1
  1. 规划好数据目录与端口:本文用 /opt/svix/ 存放 compose 文件,8071 为服务端口(被占用可改宿主侧映射端口)。
  2. 生成一个强随机 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 用两个命名卷分别持久化:

数据落点卷持久化配置
应用/端点/消息/投递记录/迁移元数据PostgreSQLpostgres-data(容器内 /var/lib/postgresql/data/)PG 自身 WAL,无需额外配置
队列任务与缓存Redisredis-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_DSNpostgresql://postgres:postgres@pgbouncer/postgresPostgreSQL 连接串(必需)
SVIX_REDIS_DSNredis://redis:6379Redis 连接串(queue/cache 用 Redis 时必需)
SVIX_JWT_SECRETq1w2e3r4(示例,生产用 32 字节随机值)JWT 认证根密钥(必需,缺失启动即报错)
SVIX_QUEUE_TYPEredis队列后端:memory/redis/rediscluster
SVIX_WHITELIST_SUBNETS[10.89.56.0/24]允许投递的内网网段(CIDR 列表字符串)
SVIX_LOG_LEVELinfo日志级别:info/debug/trace
SVIX_API_ENABLEDtrue是否运行 API 角色
SVIX_WORKER_ENABLEDtrue是否运行 Worker 角色
WAIT_FORtrue镜像启动脚本参数:等待依赖就绪(注入 --wait-for 15)
RUN_MIGRATIONS1(默认)镜像启动脚本参数:启动前自动跑库表迁移;置 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)。

步骤:

  1. 在 /opt/svix/ 创建 config.toml(最小示例):
listen_address = "0.0.0.0:8071"
log_level = "info"
whitelabel_headers = true
whitelist_subnets = ["10.89.56.0/24"]
  1. compose 中 backend 服务增加挂载:
    volumes:
      - ./config.toml:/app/config.toml:ro
    working_dir: /app
  1. 重建服务并验证生效:
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 都可以省掉。适合单机验证环境或极小规模场景。

  1. 先建网络并启动 PostgreSQL:
docker network create svix-net
docker run -d --name svix-pg --network svix-net \
  -e POSTGRES_PASSWORD=postgres \
  postgres:13.4

实测回示:输出一串 64 位容器 ID 即启动成功。

  1. 等待 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。

  1. 验证健康:
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

十二、参考链接