资讯动态

开源网关Claw云服务器部署:Docker Compose与HTTPS实践

发布时间:2026/10/21 12:27:10 来源:尧图企业网站定制
“买龙虾”这个栏目名是我随手起的没什么讲究就是想表达“把东西搞到手、折腾明白”那股劲儿。第一期要折腾的是一个在代码托管平台上小有名气的开源网关项目内部代号就叫 Claw。它能做的是把各种 Webhook、定时任务、消息推送和内部接口统一收口到一个可视化控制台里相当于给你那堆散落各处的自动化脚本装了一个总闸。我把部署环境放在国内某主流公有云的标准云服务器上机器不算新2核4G跑起来刚刚好。这篇文章既是我这次部署过程的完整记录也算是给打算自己上手跑类似项目的人一份参考。我会把从选机器、装环境、写编排文件、配域名证书到后面踩坑排查的整个过程都摊开讲哪些地方容易翻车哪些参数要提前想清楚都会说透。1. 为什么第一期选它一个“网关类”开源项目1.1 这个小工具到底能干什么很多人第一次看到 Claw 这类项目第一反应是“这不就是个消息转发工具吗”。我一开始也这么想后来真正用起来才发现它的核心价值不在转发而在“收口”。举个例子。你手上有好几个自动化流程一个监控网页价格变化一个接收服务器告警还有一个每晚会跑数据统计然后往群里发结果。正常情况下这三件事分别由三个脚本完成彼此之间没有关联报错方式也各不相同。价格脚本挂了可能没人知道服务器告警发到钉钉统计结果发到邮件管理起来非常零散。Claw 做的事情就是把这些“入口”全部接到自己身上。它内部维护一套触发器机制支持标准 Webhook 接入也支持定时轮询和队列消费。外部服务只要往它提供的地址发一个请求它就能根据你配置好的规则把消息做格式化、过滤、聚合再分发到你指定的目标——无论是某个群机器人、邮件、还是另一套内部系统。同时它自带一个还算清爽的 Web 控制台所有触发记录、分发日志、失败重试状态都能在浏览器里直接看到。这个定位有点像家里进水管的“总阀”以前每个水龙头后面都接一根水管现在所有水管先接到总阀再由总阀统一分路。好处是显而易见的排查问题的时候不需要挨个脚本翻日志对账的时候也有了一本统一的流水账。1.2 云主机选型2核4G够不够怎么算选定项目之后第一个现实问题就是拿什么样的机器来跑它。Claw 这种项目底层依赖 Docker 容器内部至少会拆出前端静态服务、后端 API、数据库三个容器如果开了队列功能还可能再加一个消息中间件。把所有容器算在一起内存占用通常在 1GB 到 1.5GB 之间。CPU 平时基本吃不满但遇到瞬时大量 Webhook 请求到来时单核会明显吃紧。所以 2核4G 这个配置对它来说不是“刚好够用”而是“留了一截余量”。1核2G 的机器其实也能启动但一旦并发上来或者数据库开始做比较重的查询系统负载会直接飘红容器可能因为内存不足被 OOM Killer 杀掉表现为服务无端重启。4G 内存的好处在于就算数据库和消息中间件同时跑起来系统仍然有将近 2GB 的可用内存docker compose 里的 healthcheck 也能稳定通过。带宽方面我选的是 5Mbps 按固定带宽计费。为什么不是按流量计费因为这个项目的使用场景以 API 请求为主消息体量很小日常带宽占用极低按流量计费看似灵活实际长时间开着反而更容易产生意外账单。5Mbps 的固定带宽足够支撑几十个 Webhook 并发接入峰值也够用。操作系统我选了 Ubuntu 22.04 LTS。没有选更新的 24.04原因很朴素22.04 的软件仓库、内核版本以及各大软件源的兼容性都经过了充分验证Docker 和 Compose 插件在上面安装最省事。很多时候部署翻车不是项目本身有问题而是系统太新依赖还没跟上。2. 部署方案背后的设计思路2.1 项目内部结构拆开看把 Claw 这个项目拉开看其实是一个典型的前后端分离结构再加上配套基础设施。前端部分是一个单页应用负责呈现控制台界面。它本身不直接访问数据库所有操作都通过调用后端暴露的 REST API 完成。这样做的好处很明显控制台只是“遥控器”真正的业务逻辑全在后端前端挂了不影响已配置的触发规则继续运行。后端服务是整个项目的核心。它承担着三块职责对外接收并校验 Webhook 请求、读取和写入配置数据、把消息按规则投递到目标渠道。由于这几块职责在代码层面是分开的所以它天然适合做成无状态服务多开几个副本也不会出现数据错乱。这也是为什么它可以被塞进容器里跑——容器化的前提就是服务本身不依赖宿主机上的特殊环境。数据层用的是 PostgreSQL 加 Redis 的组合。PostgreSQL 存的是用户配置、触发记录、投递日志这类“必须保证不丢”的数据Redis 存的是队列、限流计数这类“丢了也能重建”的临时数据。两个库职责分明备份策略也因此可以区分PostgreSQL 做每日全量备份Redis 只做持久化兜底。队列组件是可选模块。如果你的使用场景只是“收到 Webhook 就立刻转发”那消息中间件完全可以不装。但如果你需要失败重试、削峰填谷、批量聚合队列就派上用场了。这套设计思路在同类项目里很常见核心路径保持简单扩展能力留给可选模块。2.2 为什么用 docker compose 而不是其他方式确定用 docker compose 而不是裸机部署也不是无脑跟风主要基于三点考量。第一是依赖隔离。Claw 的后端对运行环境有具体要求Node.js 版本不能太旧、Python 解释器版本要匹配、系统库要装齐。如果直接装在宿主机器上今天装一个依赖明天升级一个库很容易把系统环境搞乱。容器把应用和依赖一起打包换机器部署时只要把镜像拉下来跑环境一致性天然有保障。第二是一键编排。现在这类项目基本都提供了现成的 docker-compose.yml里面把前端、后端、数据库、队列的启动顺序和网络都定义好了。我只负责填环境变量剩下的事情交给 Compose 处理。相比手动一行一行敲命令启动进程这种方式更接近“声明式”管理我要什么状态配置文件里写清楚剩下的交给工具。第三是回滚方便。裸机部署如果升级失败想回到旧版本只能靠备份或者操作系统的包管理工具过程很痛苦。容器方案就简单得多本地留着旧镜像新版本出问题就用旧镜像重新把容器拉起来。整个回滚过程在一分钟以内。当然没有万能的方案。如果这台机器上要运行几十个服务或者要支撑弹性伸缩那 docker compose 就不够用了该上 K8s 还是得上。但单机跑这种体量的项目K8s 属于杀鸡用牛刀光是控制平面的内存占用就能压垮 4G 的小机器。3. 动手前的基础环境准备3.1 SSH 登录与系统初始化拿到云主机之后第一件事不是装 Docker而是把系统基础配置好。我用 SSH 以 root 用户登录首先做的是更新软件源。这个步骤很多人会跳过但它其实很有必要——新买的机器镜像里的软件源可能指向的是某个固定版本快照不更新的话后续装包可能装到旧版本。更新完之后我立刻创建了一个普通用户并给它加了 sudo 权限。养成不用 root 直接操作的习惯是很重要的一层防线。平时登录服务器使用这个普通用户只有需要执行系统级命令时才临时用 sudo能减少很多因为手滑导致的问题。apt update apt upgrade -y adduser claw usermod -aG sudo claw接下来是登录方式的调整。云主机的默认登录方式通常是密码登录如果你在创建机器时设置了密钥那这条可以跳过如果用的是密码建议尽快把本地的公钥拷贝到服务器的 authorized_keys 文件里然后改为密钥登录。密码登录最大的风险是暴力破解哪怕你把密码设成 30 位随机串也架不住别人用字典一批一批地试。ssh-copy-id claw服务器IP改了密钥登录之后我把 SSH 服务的密码登录选项关掉了。这一步很多人不敢动觉得万一密钥丢了就连不上机器了。但实际上只要你本地保存好私钥并多做一份备份到加密U盘或者密码管理器里比每天都开着密码登录要安全得多。网络层的防火墙我选择了 ufw规则很简单放行 SSH 端口、80 和 443其余入站一律拒绝。注意如果你用的云主机开启了安全组那云平台层面的安全组和系统内部的 ufw 是两层独立的东西只有同时配置好才算完整。3.2 安装 Docker 与 Compose 插件装 Docker 这条路我建议大家优先用官方脚本但前提是你的网络能顺畅访问相关源。如果访问不畅就改用系统自带仓库里的 docker.io 包虽然版本可能旧一点但稳定够用。我在这次部署中使用的安装方式是通过系统自带的 apt 仓库安装之后再单独补装 Compose 插件。之所以不用官方脚本是因为官方脚本会添加额外的软件源在某些网络环境下反而会把事情变复杂。apt install docker.io docker-compose-v2 -y装完以后验证一下版本docker --version docker compose version看到版本号正常输出来基本就说明 Docker 环境已经就绪。接下来做两个收尾动作把 claw 用户加入 docker 组这样部署时不需要每次都在命令前面加 sudo然后设置 Docker 开机自启。systemctl enable docker usermod -aG docker claw这里有一个细节docker 组权限等同于 root。加入 docker 组的用户完全可以通过操纵容器来拿到宿主机权限。所以如果你和他人共用服务器不要轻易把别人加入 docker 组这是安全上的一个重要边界。3.3 镜像拉不下来怎么办第一次执行 docker compose up -d 时我碰到了几乎所有国内云主机用户都会遇到的问题镜像拉取速度很慢甚至卡住不动。这跟网络链路有关也和 Docker Hub 的访问策略有关属于正常现象。解决办法是给 Docker 配置镜像仓库地址也就是 registry mirror。具体操作是修改 /etc/docker/daemon.json把镜像拉取请求分流到可用的镜像源上。{ registry-mirrors: [https://docker.mirrors.example.com] }这里有个容易踩的坑不同云厂商提供的镜像源地址不一样而且这些地址可能会因为运营策略调整而变化不建议直接照抄网上的某个地址最好去你所使用的云平台文档里查一下当前可用的镜像仓库地址。配好之后重启 Dockersystemctl restart docker再拉镜像速度通常会快很多。另外提醒一句镜像源只影响镜像拉取这个环节容器运行时的网络仍然正常不会对后续请求产生什么负面影响。4. 核心部署流程从目录到服务跑通4.1 目录规划与配置文件环境搞定之后我开始搭项目目录。这里我的习惯是先把目录结构规划好再写配置文件避免后面东一个文件西一个文件找起来麻烦。我采用的目录结构是/opt/claw/ ├── docker-compose.yml ├── .env ├── data/ │ ├── postgres/ │ ├── redis/ │ └── uploads/ └── caddy/ └── Caddyfile项目放在 /opt/claw 下面而不是 root 家目录或者 /home 下原因是 /opt 专门用于存放第三方软件权限清晰不容易和用户个人文件混在一起。数据量的目录单独划出来所有容器产生的持久化数据都落在这里将来做备份只需要打包这一个目录。docker-compose.yml 是这个部署的核心文件。项目官方提供的版本通常已经定义好了所有服务我做的事情是基于自己的实际情况做微调。关键配置项包括镜像版本、端口映射、存储卷挂载、环境变量引用。我这里用了一个值得推荐的小技巧所有敏感配置比如数据库密码、JWT 签名密钥都不直接写在 docker-compose.yml 里而是放在同目录下的 .env 文件中然后在 compose 里用${VARIABLE}方式引用。好处是 docker-compose.yml 可以放心提交到代码仓库而 .env 留在服务器本地泄露风险大大降低。4.2 启动服务与自检配置写完之后先用一条命令做配置检查docker compose config这个命令会把你配置里的所有变量替换成实际值然后输出完整的解析结果。如果语法有误或者引用变量不存在会在这里直接报错避免在真正启动时才暴露问题。确认无误后执行启动命令docker compose up -d第一次启动需要拉取多个镜像加上初始化数据库、安装依赖整个过程会比较慢耐心等即可。启动结束后我习惯先看一遍容器状态docker compose ps正常情况下所有服务都应该是 running运行中状态。如果有容器显示 Restarting重启中基本可以断定是配置或者依赖出了问题需要立刻看日志。docker compose logs -f backend日志里出现listening on 0.0.0.0:8080或者类似的提示说明后端服务已经正常监听端口。接着再验证一下健康检查接口curl http://localhost:8080/healthz返回ok或者{status:healthy}基本就能确定整个后端链路是通的。前端控制台如果映射在 9000 端口在本地浏览器访问一下能正常打开登录页整个部署就算完成了一半。4.3 备份、升级与日常维护服务跑通只是开始更重要的其实是后面这些“看不见”的日常维护。我在这台机器上配置了两类备份。第一类是数据库自动备份。PostgreSQL 数据是服务的命根子我用系统自带的 cron 写了一条每日备份任务凌晨 3 点执行 pg_dump把数据导成 SQL 文件再按日期压缩归档保留最近 14 天的备份。0 3 * * * cd /opt/claw docker compose exec -T db pg_dump -U claw claw_db | gzip /backup/pg_$(date \%F).sql.gz第二类是配置备份。docker-compose.yml、.env、Caddyfile 这几个文件体积很小但价值极高我直接用脚本打包同步到另一台存储机器上。这样即使整台云主机出问题也能在新机器上快速重建环境。升级流程方面我总结出一套固定操作顺序先备份数据库再拉取新镜像然后执行 docker compose up -d 让 Compose 自动重建配置有变化的容器最后验证健康检查接口和页面状态。整个过程熟练的话五分钟内完成相比裸机升级要省事得多。唯一的提醒是升级前一定看一眼官方发布的版本说明如果存在不兼容的配置变更提前处理否则容器可能反复重启。5. 对外提供服务域名、HTTPS 与安全加固5.1 Caddy 反代与自动 HTTPS服务在本地跑通了但要让外部系统能够访问并调用还需要通过域名对外提供入口。出于安全考虑我坚决不建议直接对外开放 8080 这类端口因为裸奔的 HTTP 服务既没有加密也容易被扫描器盯上。我使用的工具是 Caddy。相比传统的 Nginx 加 Certbot 组合Caddy 最大的优势是自动申请和续期 HTTPS 证书。传统方式需要手动配置 SSL 证书路径还需要额外写定时续期脚本Caddy 只需要一行配置它会自动向证书签发机构申请证书到期后自动续期完全不用操心。我的 Caddyfile 是这样写的claw.example.com { reverse_proxy :9000 }就这短短两行Caddy 启动后就会自动为 claw.example.com 申请一张 HTTPS 证书然后把公网进来的 443 流量转发到本机 9000 端口的控制台服务上。这里值得注意的点是域名解析需要提前在 DNS 服务商那里把 A 记录指向云主机的公网 IP并且等待解析生效。如果域名没有正确解析Caddy 在申请证书的阶段就会报错因为证书签发机构需要确认“这个域名确实属于你”。Caddy 的部署方式也很简单我在 compose 文件里单独加了一个 Caddy 服务把主机的 80 和 443 端口映射进去再把 Caddyfile 挂载进容器。这样一来所有进出流量都统一经过 Caddy它既扮演了反向代理的角色又顺带解决了 HTTPS架构非常干净。5.2 安全组和防火墙最小化网络安全上我一直坚持“最小权限原则”能用到的端口才开放用不到的端口一律关闭。云平台的安全组和系统内部的 ufw 是两层防线都要配置。云平台安全组我设置的规则如下协议端口来源用途TCP22仅限我的办公网 IPSSH 登录TCP800.0.0.0/0HTTP 跳转TCP4430.0.0.0/0HTTPS 访问系统内部的 ufw 也做了同样的配置。两层防火墙的好处是即使某一层配置出错另一层仍然能够挡住大部分非法访问。数据库端口我完全没有对外开放。PostgreSQL 的 5432 端口只绑定在 Docker 内部网络上外边即使扫描到这个端口也无法直接连接。需要数据库操作时我都是通过 docker compose exec 进入容器内部执行这种方式在安全性上比开放远程连接要好得多。同样不对外的还有 Redis 的 6379 端口。Redis 这类内存数据库如果暴露在公网上且未设置密码非常容易被恶意访问。我的原则是凡是能被 Docker 网络内部互联访问的服务一律不映射到宿主机端口。5.3 端口暴露与密钥管理再强调一次端口暴露原则因为这个问题太容易出事了。docker-compose.yml 里会看到两种端口写法8080:8080和127.0.0.1:8080:8080。前者把端口暴露到所有网卡上外部可以直接访问后者只暴露到本机回环地址只有服务器本机上的进程才能访问。如果你需要让容器端口仅供本机上的反向代理访问一定要写明回环地址前缀避免被公网直接访问。密钥管理上我强烈建议把敏感信息集中在 .env 文件里并且权限设置成只有当前用户可读chmod 600 /opt/claw/.env另外环境变量里如果出现 JWT 密钥、API 密钥这类长期有效的凭证建议安装一个密码管理器来存放备份确保即使服务器完全丢失也能恢复全部身份凭证。6. 常见问题和调试实录6.1 容器频繁重启第一次部署时最容易遇到的现象就是容器反复重启表现为 docker compose ps 里某个容器一直显示 Restarting或者 STATUS 后面跟着restarting。最常见的原因是内存配置不当。Claw 后端基于 Node.js 运行时默认内存上限通常不设限制但如果宿主机内存只有 2G系统可能会在内存耗尽时强制杀掉容器。排查方法很简单看日志。docker compose logs backend日志末尾出现FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory基本可以断定是 Node 进程内存超限。解决办法是给容器显式设置内存限制或者调大 Node 的堆内存上限。我后来在 compose 配置里加了mem_limit: 1g重启容器问题就再没出现过。另一种常见情况是数据库还没初始化完成后端就提前启动了导致后端连接数据库失败而退出。这种情况日志里会显示connection refused。解决办法很简单在 compose 配置里利用 healthcheck 让后端等待数据库健康后再启动或者在启动脚本里加一段等待重试逻辑。6.2 页面配置不生效服务跑起来之后还有一类“软故障”很让人抓狂控制台页面明明加载出来了但怎么改配置都不生效刷新页面之后设置又变回原样。第一反应是检查浏览器网络请求。打开控制台的开发者工具看配置保存时提交的 API 请求是否返回了200。如果请求失败说明问题在前后端接口层。如果请求成功但刷新后不生效大概率是数据没有成功写入数据库这时去查看后端日志看看 SQL 语句执行是否报错。还有一类隐蔽问题时区不一致。容器默认时区是 UTC而你的本机时间可能是东八区。如果你设置的定时任务是基于本地时间但容器内部按照 UTC 计算就会出现任务执行时间整体偏移 8 小时的情况。解决办法是在 compose 的环境变量里加上TZAsia/Shanghai并重启容器。我前后排查这个问题花了差不多半小时最后发现罪魁祸首就是容器时区没设置。整个过程虽然有点浪费时间但也让我记住了凡是涉及定时调度的容器第一时间先把时区变量加上。6.3 证书申请失败Caddy 自动申请证书失败是另一个高频问题。现象通常是访问域名返回 ERR_CONNECTION_REFUSED 或者“不安全连接”Caddy 日志里出现failed to obtain certificate。排查顺序我建议这样来先在本地执行dig claw.example.com确认域名解析已经指向服务器公网 IP。然后确认云平台安全组和系统防火墙是否真的放行了 80 端口。证书签发机构在域名验证阶段会访问你的 80 端口如果这个端口不可达申请必然失败。再有就是时区问题在证书系统里的影响Caddy 所在容器如果时间不准向证书签发机构发请求时可能被认为请求无效。解决方法是确保服务器时间同步正常并且容器内时间与宿主机一致。最后一种情况比较冷门你申请的域名之前被别人使用过记录了某些异常的验证缓存导致签发机构延迟确认。这种情况没什么好办法只能等待或者换个临时域名测试。把这三类问题理清楚之后我发现它们都有一个共同特征真正出问题的都不是项目本身而是周边基础设施。网络不通看链路应用起不来看日志证书失败看端口这些排查经验在别的项目里同样适用。务农的都知道地里的庄稼长不好多半不是种子的问题而是土壤、水分和光照的条件不对。部署服务也是这个道理。我第一次折腾这类云上项目时最深的体会就是不要急着改代码先把运行环境梳理干净问题往往能解决一大半。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑