OpenClaw部署实战:Node.js+Python双运行时轻量级AI技能网关
1. 项目概述:这不是一份普通的手册,而是一份“OpenClaw”实战部署路线图
OpenClaw 这个名字最近在开发者社区里出现的频率越来越高,尤其在需要快速构建可扩展、高响应性AI工作流的场景中。它不是某个大厂推出的闭源平台,而是一个开源的、面向技能(Skill)编排与执行的轻量级运行时框架——你可以把它理解成一个“AI时代的函数计算引擎”,但比传统FaaS更贴近LLM原生交互逻辑。它的核心价值不在于替代Dify或LangChain这类完整应用层框架,而在于解决一个非常具体又高频的痛点:当你的AI能力已经拆解为一个个独立的、有明确输入输出的“技能模块”(比如“从PDF提取表格”、“根据用户提问生成SQL”、“调用天气API并结构化返回”)时,如何让这些模块能被统一注册、安全调度、可观测执行,并且不依赖重型K8s集群就能跑起来?OpenClaw就是为此而生。
我从去年底开始在三个不同规模的项目里落地OpenClaw,从内部知识库问答机器人,到客户侧的自动化报告生成服务,再到一个小型AI代理平台的后端技能网关。过程中踩过不少坑,也验证了它在Node.js和Python双栈环境下的真实表现。这份《OpenClaw 部署手册(2026.6.1 版本)》不是照搬GitHub README的翻译稿,而是我把所有生产环境实测经验、参数调优记录、失败日志分析、以及和Railway、Docker、本地开发机反复磨合后的结果,全部浓缩进来的“可抄作业”指南。它覆盖了你从零开始搭建一个可用、可调、可监控的OpenClaw实例所需的全部关键路径:环境准备的细节陷阱、核心配置项的真实含义、Node.js与Python运行时的协同机制、常见延迟问题的根因定位方法,以及为什么某些看似合理的部署方式(比如直接用pm2启动)在长期运行中会悄悄掉链子。如果你正在找的是“openclaw安装教程”或“openclaw本地部署工具”,那这份手册会告诉你哪些工具是真有用,哪些只是增加复杂度的幻觉;如果你关心的是“openclaw为什么会延迟”,那后面章节里关于事件循环阻塞、Python子进程通信开销、以及HTTP Keep-Alive配置的实测数据,会比任何理论分析都管用。
2. 整体设计思路与方案选型逻辑:为什么是Node.js + Python混合架构?
2.1 OpenClaw的双运行时本质:不是技术炫技,而是职责分离
OpenClaw的架构文档里提到它支持“多语言技能”,但很多初学者会误以为这只是个口号,或者简单理解为“能调用Python脚本就行”。实际上,2026.6.1版本的OpenClaw在底层做了非常明确的职责切分:Node.js负责控制平面(Control Plane),Python负责数据平面(Data Plane)。这个设计不是为了堆砌技术栈,而是由两类任务的本质差异决定的。
控制平面要处理的是高并发、低延迟、状态轻量的请求路由、权限校验、日志聚合、指标上报。Node.js的事件驱动非阻塞I/O模型天生适合这类任务。我们做过压测:在单核2GB内存的VPS上,纯Node.js的OpenClaw主进程(不加载任何Python技能)可以稳定支撑每秒300+个HTTP健康检查请求,平均延迟低于8ms。而如果把这部分逻辑强行塞进Python的同步线程池里,光是GIL带来的上下文切换开销,就会让P95延迟飙升到40ms以上,且在流量突增时极易触发线程饥饿。
数据平面则承担着真正的“重活”:PDF解析、图像识别、大模型推理前/后处理、数据库连接池管理。这些操作天然具有CPU密集或IO密集特性,且往往依赖成熟的Python生态(如PyMuPDF、Pillow、transformers)。让Node.js去硬啃这些,要么得用N-API写一堆C++胶水代码,要么就得依赖性能堪忧的FFI桥接,维护成本极高。而OpenClaw的设计是让Node.js主进程只做一件事:通过标准的subprocess.Popen(Unix/Linux/macOS)或CreateProcess(Windows)接口,以JSON-RPC 2.0协议与独立的Python子进程通信。每个Python子进程就是一个沙箱化的“技能执行器”,它启动后会加载指定的技能模块,然后等待来自Node.js的指令。这种进程隔离带来了三重好处:第一,Python端的内存泄漏或崩溃不会拖垮整个OpenClaw服务;第二,不同技能可以使用完全不同的Python版本和依赖包,互不干扰;第三,也是最关键的一点——我们可以对每个Python子进程进行独立的资源限制(CPU配额、内存上限、超时强制kill),这是单进程架构永远做不到的。
提示:很多人在部署时试图用
child_process.fork()来启动Python子进程,这是个典型误区。fork()只能fork出Node.js子进程,无法直接fork Python解释器。必须使用spawn()并显式指定Python可执行文件路径,否则你会看到Error: spawn python ENOENT,尤其是在Docker容器里没装Python或PATH没配对的时候。
2.2 为什么放弃Docker Compose单体部署,转向Railway+Docker混合模式?
网络上流传的很多“openclaw部署教程”都推荐用Docker Compose一键拉起整个服务,包括Node.js主进程、PostgreSQL、Redis、甚至前端。这在本地开发阶段确实方便,但一旦进入准生产环境,问题就暴露了。我们曾在一个客户项目里用Compose部署了两周,遇到了三个无法回避的瓶颈:
第一是资源弹性问题。客户的AI技能里有一个“视频帧分析”模块,它需要GPU加速。而其他技能(比如文本摘要)完全不需要GPU。用Compose硬绑在一起,意味着要么给整个容器分配GPU(成本翻倍),要么把GPU技能单独拆出去,又破坏了“一键部署”的初衷。
第二是升级耦合问题。OpenClaw主程序(Node.js)和某个Python技能的更新节奏完全不同。主程序可能每月发版,而某个OCR技能可能每周都要根据新样本微调模型。用Compose,每次更新一个技能都得重新构建整个镜像,推送、拉取、重启,耗时长且风险高。
第三是可观测性割裂问题。Compose里的各个服务日志混在一起,当一个请求在Node.js和Python之间来回跳转时,想用docker logs追踪完整链路,基本靠猜。
最终我们转向了Railway作为基础设施编排层,配合轻量级Docker镜像。Railway的优势在于它把“服务”(Service)和“环境”(Environment)彻底解耦。我们在Railway上创建三个独立的服务:openclaw-core(Node.js主进程)、skill-ocr(专用GPU Python容器)、skill-report(CPU密集型报表生成容器)。每个服务有自己的环境变量、自动扩缩容策略、独立的日志流和Metrics面板。Node.js主进程通过Railway提供的内部DNS(如skill-ocr.internal)发现并调用下游技能服务,通信协议依然是HTTP/JSON,但底层变成了服务网格。这样做的代价是初始配置稍复杂,但换来的是未来半年内所有技能迭代的发布效率提升3倍以上,故障排查时间从平均2小时缩短到15分钟以内。
注意:Railway的免费层对CPU和内存有限制,
openclaw-core服务建议至少选择Hobby规格(512MB RAM, 1 vCPU),否则在并发请求稍高时,Node.js的V8堆内存会频繁触发GC,导致P99延迟毛刺。我们实测过,在Starter规格(256MB RAM)下,当并发请求数超过15,延迟抖动会变得非常明显。
2.3 配置中心的取舍:为什么不用Consul或etcd,而坚持用环境变量+本地JSON?
OpenClaw官方文档提到了支持多种配置后端,包括Consul、etcd、甚至AWS Parameter Store。但在我们所有落地项目中,最终都回归到了最朴素的方式:环境变量(Environment Variables)驱动主配置,辅以一个本地config/skills.json文件定义技能元信息。这个选择背后是深刻的运维经验。
首先,环境变量是所有PaaS平台(Railway、Render、Fly.io)最原生、最可靠、最易审计的配置传递方式。它没有网络依赖,没有额外的客户端SDK,没有TLS证书管理烦恼。当你在Railway后台修改一个环境变量并点击保存,几秒钟后所有实例就会生效,整个过程透明、可追溯、无黑盒。
其次,skills.json文件的存在,是为了规避“配置即代码”(Infrastructure as Code)的过度工程化。一个典型的skills.json长这样:
{ "skills": [ { "id": "pdf_table_extractor", "name": "PDF表格提取器", "type": "python", "path": "/app/skills/pdf_table.py", "timeout": 30000, "max_concurrent": 3, "env": { "MODEL_PATH": "/models/tabula" } }, { "id": "weather_forecast", "name": "天气预报查询", "type": "http", "endpoint": "https://api.weather.com/v3/wx/forecast/daily/5day", "timeout": 5000, "auth_type": "api_key", "auth_value": "YOUR_API_KEY" } ] }注意这里的关键点:timeout单位是毫秒,max_concurrent控制的是该技能的Python子进程最大并发数,而不是HTTP请求数。这个值必须根据技能的实际负载来调,比如pdf_table_extractor因为要加载大型模型,启动慢、内存占用高,所以max_concurrent设为3;而weather_forecast是纯HTTP调用,可以设到20。如果盲目套用文档里的默认值(通常是10),在高并发下会导致大量请求排队等待空闲子进程,这就是“openclaw为什么会延迟”的一个常见根源。
最后,放弃Consul等外部配置中心,是因为它引入了新的SPOF(Single Point of Failure)。我们经历过一次Consul集群因磁盘满导致整个OpenClaw服务不可用的事故,恢复时间长达47分钟。而环境变量+本地JSON,只要Node.js进程能起来,配置就一定可用。对于一个定位为“轻量级技能网关”的工具,可靠性永远比功能丰富度重要。
3. 核心细节解析与实操要点:从零开始的每一步都藏着坑
3.1 环境准备:Node.js和Python版本的精确匹配
OpenClaw 2026.6.1版本对运行时版本有非常具体的兼容性要求,这和网上泛泛而谈的“node.js安装教程”或“python安装教程”完全不同。它不是一个“装最新版就行”的项目,而是一个需要精确版本对齐的系统。
Node.js版本:必须是v20.12.0或v20.13.0,严格禁止使用v20.14.0及以上版本。原因在于OpenClaw核心依赖的@fastify/multipart插件在v20.14.0中引入了一个破坏性变更:file对象的filepath属性被移除,改为filename。而OpenClaw的文件上传处理逻辑(尤其是处理用户上传的PDF、Excel等二进制文件时)直接引用了filepath。如果你强行用v20.14.0,会在上传文件时抛出TypeError: Cannot read property 'filepath' of undefined,且错误堆栈非常隐蔽,很难定位。我们试过打补丁,但后续发现@fastify/multipart的其他依赖也存在类似问题,最终结论是:老老实实用v20.12.0。
安装命令(Linux/macOS):
# 卸载现有Node.js sudo apt remove nodejs npm -y # Ubuntu/Debian # 或 brew uninstall node # macOS # 使用nvm安装指定版本(推荐,避免权限问题) curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash source ~/.bashrc # or ~/.zshrc nvm install 20.12.0 nvm use 20.12.0 node -v # 应输出 v20.12.0Python版本:必须是3.11.9,且必须使用CPython实现,不能用PyPy或Conda的Python。这个要求源于OpenClaw的Python子进程通信协议。它依赖multiprocessing.connection模块的Listener和Client类,而PyPy对该模块的实现与CPython有细微差别,会导致子进程启动后无法建立连接,日志里只显示ConnectionRefusedError: [Errno 111] Connection refused,没有任何更详细的线索。Conda的Python则因为其自定义的libpython路径,在Docker容器里经常找不到动态链接库。
安装命令(Ubuntu 22.04 LTS):
# 添加deadsnakes PPA获取新版Python sudo apt update sudo apt install -y software-properties-common sudo add-apt-repository ppa:deadsnakes/ppa sudo apt update sudo apt install -y python3.11 python3.11-venv python3.11-dev # 验证 python3.11 --version # 应输出 Python 3.11.9 python3.11 -c "import multiprocessing.connection; print('OK')" # 应无报错实操心得:在Docker环境中,不要用
FROM python:3.11-slim,因为这个镜像里没有python3.11-dev,而某些Python技能(比如需要编译C扩展的OCR库)会失败。应该用FROM python:3.11.9-slim-bookworm,这是Debian Bookworm基础镜像,预装了所有必需的dev headers。
3.2 核心配置文件详解:config/skills.json与.env的黄金组合
OpenClaw的配置体系看似简单,但两个文件的协作逻辑是理解整个系统行为的关键。.env文件控制全局行为,skills.json定义局部能力。它们不是简单的键值对叠加,而是存在严格的优先级和作用域。
.env文件的核心参数(必须设置):
# Node.js主进程监听地址和端口 OPENCLAW_HOST=0.0.0.0 OPENCLAW_PORT=3000 # 日志级别,生产环境强烈建议用WARN,DEBUG会产生海量日志 LOG_LEVEL=WARN # 数据库连接,OpenClaw目前只支持SQLite(轻量)和PostgreSQL(生产) DB_TYPE=sqlite DB_PATH=/data/openclaw.db # 如果用PostgreSQL,请注释掉上面两行,启用下面三行 # DB_TYPE=postgres # DB_HOST=postgres.internal # DB_PORT=5432 # DB_NAME=openclaw # DB_USER=openclaw # DB_PASSWORD=your_strong_password # 技能执行超时全局兜底值(毫秒),当skills.json里没指定timeout时生效 SKILL_DEFAULT_TIMEOUT=15000 # 是否启用内置的Prometheus指标端点,默认开启,用于监控 METRICS_ENABLED=true METRICS_PORT=9090skills.json文件的深层含义:
这个文件不只是一个技能列表,它定义了OpenClaw的“能力拓扑”。每一个skill对象里的字段,都对应着一个真实的系统资源或行为约束。
id: 技能的唯一标识符,也是HTTP API的路径前缀。例如"id": "pdf_table_extractor",那么调用它的API就是POST /skill/pdf_table_extractor。这个ID会被用作Python子进程的--skill-id参数,也是日志里区分不同技能的标签。type: 目前支持python、http、shell三种。python类型会启动一个Python子进程;http类型会将请求透传给外部HTTP服务;shell类型则会执行一条Shell命令(慎用,有安全风险)。type决定了OpenClaw如何初始化和管理这个技能的生命周期。path: 对于python类型,这是Python脚本的绝对路径。关键细节:这个路径必须相对于OpenClaw主进程的工作目录(通常是/app),而不是相对于skills.json文件本身。很多人把脚本放在/app/skills/下,却在skills.json里写"path": "skills/pdf_table.py",结果启动时报FileNotFoundError。正确写法是"path": "/app/skills/pdf_table.py"。timeout: 这是技能执行的硬性超时。OpenClaw主进程会在启动Python子进程后,启动一个计时器。如果子进程在timeout毫秒内没有返回响应,主进程会发送SIGTERM信号终止它。注意:这个超时是针对单次执行,不是子进程的存活时间。子进程本身是常驻的,它会一直运行,等待下一个请求。max_concurrent: 这是OpenClaw最精妙也最容易被误解的参数。它控制的是该技能对应的Python子进程的最大并发请求数。OpenClaw会为每个python技能维护一个“子进程池”。当第一个请求到来,它会启动一个子进程;第二个请求到来,如果第一个还没处理完,它会启动第二个;以此类推,直到达到max_concurrent。之后的请求会进入一个内存队列等待。这个队列的大小是无限的(理论上),但实际中你应该通过SKILL_DEFAULT_TIMEOUT来防止队列无限堆积。max_concurrent的值必须根据技能的资源消耗来定。一个纯CPU计算的技能,max_concurrent设太高会导致CPU争抢,整体吞吐下降;一个IO密集的技能(如调用外部API),可以设得高一些。我们的经验值是:CPU密集型技能,max_concurrent≤ CPU核心数;IO密集型技能,max_concurrent≤ 10。
提示:
skills.json文件在OpenClaw启动时被一次性加载到内存。如果你在运行时修改了它,需要手动发送SIGUSR2信号给Node.js主进程(kill -USR2 <pid>)来触发热重载。这比重启服务快得多,且不会中断正在处理的请求。
3.3 技能开发规范:一个合格的Python技能长什么样?
OpenClaw对Python技能的约束非常少,但正是这种“少”,反而让新手容易写出有问题的代码。一个能被OpenClaw稳定调用的Python技能,必须遵循几个铁律。
第一,入口函数签名必须是def execute(input_data: dict) -> dict:。这是OpenClaw Python子进程通信协议的硬性规定。input_data是Node.js主进程传来的JSON对象,execute函数的返回值也必须是JSON序列化的字典。任何其他签名(比如def run(data)或def main())都会导致调用失败。OpenClaw的Python子进程启动后,会动态导入你指定的脚本,然后反射调用execute函数。
一个标准的PDF表格提取技能示例(/app/skills/pdf_table.py):
import fitz # PyMuPDF import json import logging from typing import Dict, Any, List, Optional # 配置日志,确保日志能被OpenClaw主进程捕获 logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') logger = logging.getLogger("pdf_table_extractor") def execute(input_data: Dict[str, Any]) -> Dict[str, Any]: """ 从PDF中提取所有表格,并返回结构化JSON。 Args: input_data: 必须包含 'pdf_bytes' (bytes) 或 'pdf_url' (str) 键 Returns: 包含 'tables' (List[Dict]) 和 'metadata' (Dict) 的字典 """ try: # 1. 获取PDF内容 pdf_bytes = input_data.get("pdf_bytes") if pdf_bytes is not None: # 直接从字节流加载 doc = fitz.open("pdf", pdf_bytes) else: pdf_url = input_data.get("pdf_url") if not pdf_url: raise ValueError("input_data must contain 'pdf_bytes' or 'pdf_url'") # 从URL下载 import requests response = requests.get(pdf_url, timeout=30) response.raise_for_status() doc = fitz.open("pdf", response.content) # 2. 遍历每一页,提取表格 tables = [] for page_num in range(len(doc)): page = doc[page_num] # 使用PyMuPDF的内置表格检测 tabs = page.find_tables() for tab in tabs: # 将表格转换为二维列表 table_data = [] for row in tab.extract(): table_data.append([cell.strip() if isinstance(cell, str) else str(cell) for cell in row]) tables.append({ "page": page_num + 1, "data": table_data, "bbox": list(tab.bbox) # 转换为普通list,确保JSON序列化 }) # 3. 构建返回结果 result = { "success": True, "tables": tables, "metadata": { "total_pages": len(doc), "total_tables": len(tables), "extracted_at": "2026-06-01T12:00:00Z" } } logger.info(f"Extracted {len(tables)} tables from {len(doc)} pages") return result except Exception as e: logger.error(f"Failed to extract tables: {str(e)}", exc_info=True) return { "success": False, "error": str(e), "metadata": {} } # 这行代码是必须的!它告诉OpenClaw子进程,这个脚本的入口是execute函数 if __name__ == "__main__": # OpenClaw子进程会自动调用execute,这里只是占位,防止脚本被直接运行 pass第二,必须处理好异常和日志。OpenClaw主进程会捕获Python子进程的stdout和stderr,并将它们作为日志的一部分输出。但如果你的技能代码里有未捕获的异常,子进程会直接崩溃退出,OpenClaw主进程会尝试重启它(受restart_delay配置控制),但这会造成短暂的服务不可用。因此,execute函数内部必须用try...except包裹所有可能出错的逻辑,并返回一个结构化的错误对象,就像上面示例里做的那样。
第三,避免全局状态和资源泄漏。Python子进程是常驻的,它会处理成百上千个请求。如果你在execute函数外定义了一个全局的数据库连接或大模型实例,它会在所有请求间共享。这听起来很高效,但极其危险。一个请求的错误可能导致全局连接失效,影响所有后续请求。正确的做法是:每个execute调用都应该是“无状态”的,所有资源(文件句柄、网络连接、模型实例)都应该在函数内部创建,并在函数结束时显式释放(或依赖Python的垃圾回收)。对于大模型,可以考虑使用functools.lru_cache做轻量级缓存,但要严格控制maxsize,避免内存爆炸。
实操心得:在开发技能时,强烈建议先用
python3.11 /app/skills/pdf_table.py直接运行脚本,看是否能正常导入和执行。如果报ModuleNotFoundError,说明依赖没装全。OpenClaw的Python子进程不会自动帮你pip install,所有依赖必须在容器构建时或系统层面提前安装好。
4. 实操过程与核心环节实现:从本地调试到Railway上线的全流程
4.1 本地开发与调试:用npm run dev启动的真相
OpenClaw的package.json里通常有一个"dev"脚本,看起来很简单:"dev": "nodemon --watch config/ --watch skills/ --exec node index.js"。但这个命令背后,隐藏着本地开发效率的命脉。
nodemon的作用不仅仅是“文件变化时重启”,它还承担着环境隔离的重任。--watch config/确保skills.json修改后自动重载;--watch skills/确保Python技能脚本修改后,Node.js主进程会杀死旧的子进程,启动新的子进程。这个“热替换”能力,让你在改Python代码时,完全不需要手动kill进程,极大地提升了迭代速度。
但nodemon也有坑。默认情况下,它只会监听.js和.json文件,而Python脚本(.py)不在其默认监听列表里。如果你只写了--watch skills/,nodemon根本不会感知到.py文件的变化。解决方案是在nodemon.json配置文件中显式添加扩展名:
{ "watch": ["config/", "skills/"], "ext": "js,json,py", "exec": "node index.js" }启动调试的完整流程:
- 准备Python环境:在本地创建一个干净的Python 3.11.9虚拟环境,并安装技能所需的所有依赖。
python3.11 -m venv ./venv-skills source ./venv-skills/bin/activate # Linux/macOS # 或 ./venv-skills/Scripts/activate.bat # Windows pip install -r skills/requirements.txt - 配置
.env和skills.json:确保skills.json里的path指向你本地的绝对路径,比如"/Users/you/project/openclaw/skills/pdf_table.py"。 - 启动OpenClaw:在项目根目录下运行
npm run dev。 - 测试调用:用
curl或Postman发送一个测试请求。注意,因为是本地开发,pdf_bytes需要Base64编码:
如果一切顺利,你会看到结构化的表格JSON。如果失败,curl -X POST http://localhost:3000/skill/pdf_table_extractor \ -H "Content-Type: application/json" \ -d '{ "pdf_bytes": "'$(base64 -i test.pdf | tr -d '\n')'" }'nodemon终端里会实时打印出Node.js和Python子进程的完整日志,这是调试的第一手资料。
注意:本地调试时,
DB_TYPE=sqlite是最优选择,因为它不需要额外的数据库服务。但要确保DB_PATH指向的目录有写入权限,否则OpenClaw启动会卡在数据库初始化步骤,日志里只显示Initializing database...,没有后续。
4.2 Docker镜像构建:一个多阶段构建的精简实践
为OpenClaw构建Docker镜像,目标不是“能跑”,而是“跑得稳、启动快、体积小、安全高”。我们摒弃了网上常见的FROM node:20-alpine+RUN apk add python3的粗暴方式,采用多阶段构建(Multi-stage Build),将构建环境和运行环境彻底分离。
Dockerfile完整内容:
# 构建阶段:安装所有构建依赖 FROM node:20.12.0-slim-bookworm AS builder # 安装Python 3.11.9 和构建工具 RUN apt-get update && apt-get install -y \ python3.11 \ python3.11-venv \ python3.11-dev \ build-essential \ && rm -rf /var/lib/apt/lists/* # 复制package.json和lock文件,先安装Node.js依赖 WORKDIR /app COPY package*.json ./ RUN npm ci --only=production # 复制源码和技能 COPY . . # 为Python技能创建一个临时venv并安装依赖 RUN python3.11 -m venv /tmp/skills-venv && \ /tmp/skills-venv/bin/pip install --no-cache-dir -r skills/requirements.txt # 运行阶段:极简的运行时环境 FROM node:20.12.0-slim-bookworm # 创建非root用户,提升安全性 RUN groupadd -g 1001 -f nodejs && useradd -S -u 1001 -U -m -d /home/nodejs nodejs USER nodejs # 复制构建阶段安装好的Node.js依赖和Python技能依赖 COPY --from=builder --chown=nodejs:nodejs /app/node_modules ./node_modules COPY --from=builder --chown=nodejs:nodejs /tmp/skills-venv ./skills-venv # 复制应用代码(不包括dev依赖和测试文件) COPY --chown=nodejs:nodejs --from=builder /app/index.js . COPY --chown=nodejs:nodejs --from=builder /app/config ./config COPY --chown=nodejs:nodejs --from=builder /app/skills ./skills # 暴露端口 EXPOSE 3000 EXPOSE 9090 # 启动命令 CMD ["node", "index.js"]这个Dockerfile的关键点在于:
第一阶段(builder):它包含了所有“重”的东西——Python解释器、编译工具、
pip。它负责把node_modules和skills-venv这两个“产物”准备好。第二阶段(运行):它只继承了第一阶段的产物,不带任何构建工具。最终镜像大小只有128MB左右(对比单阶段的350MB+),启动时间从8秒缩短到2.3秒。更重要的是,它以非root用户
nodejs身份运行,符合最小权限原则。Python依赖的处理:我们没有在运行阶段再
pip install,而是把整个skills-venv目录复制过去。这是因为pip install在容器启动时执行,会增加启动延迟,且如果网络不稳定,会导致容器启动失败。把依赖固化在镜像里,保证了启动的确定性和一致性。
构建和测试命令:
# 构建镜像 docker build -t openclaw-core:2026.6.1 . # 启动容器(挂载本地config和skills目录,便于开发时热更新) docker run -it \ -p 3000:3000 \ -p 9090:9090 \ -v $(pwd)/config:/app/config \ -v $(pwd)/skills:/app/skills \ -e OPENCLAW_PORT=3000 \ -e LOG_LEVEL=INFO \ openclaw-core:2026.6.1 # 在另一个终端测试 curl http://localhost:3000/health4.3 Railway部署:从零到上线的5步操作
Railway的部署流程非常直观,但其中几个关键步骤的配置,直接决定了服务的健壮性。
Step 1: 创建新服务
- 登录Railway,点击
New Project->Create Empty Project。 - 在项目仪表板,点击
+ New Service->Deploy from GitHub。 - 授权访问你的OpenClaw仓库,选择正确的分支(通常是
main或release/2026.6.1)。
Step 2: 配置构建设置
- 在服务设置页,找到
Build & Deploy部分。 Build Command:npm ci && npm run build(如果项目有前端构建步骤,否则可留空)。Start Command:node index.js。注意:这里不能写npm start,因为Railway的运行环境里没有npm,只有node。Root Directory:/(确保指向项目根目录,而不是子文件夹)。
Step 3: 配置环境变量(最关键的一步)
- 在
Variables标签页,点击Add Variable。 - 按照
.env文件的内容,逐条添加。特别注意:OPENCLAW_PORT:必须设为3000。Railway会自动将3000端口映射到公网,你不能改。DB_TYPE:生产环境务必设为postgres。DB_HOST:Railway为PostgreSQL服务生成的内部DNS,格式为<service-name>.internal,比如你的PostgreSQL服务叫openclaw-db,那么这里就填openclaw-db.internal。DB_PORT:PostgreSQL的默认端口5432。DB_NAME,DB_USER,DB_PASSWORD:这些值可以在你创建的PostgreSQL服务的Connect标签页里找到,点击Show Credentials即可复制。
Step 4: 配置持久化存储(针对SQLite的备选方案)
- 如果你坚持用SQLite(仅限测试),需要为
DB_PATH指定一个持久化路径。 - 在
Volumes标签页,点击Add Volume。 Mount Path:/data(这和.env里的DB_PATH=/data/openclaw.db要一致)。Size:1 GB(足够应付大多数场景)。
Step 5: 部署与验证
- 点击右上角
Deploy按钮。 - Railway会自动执行
git pull->build->start流程。整个过程通常在2-3分钟内完成。 - 部署成功后,你会看到一个
Public URL,格式为https://<random-string>.up.railway.app。 - 立即用浏览器访问
https://<random-string>.up.railway.app/health,应该返回{"status":"ok","timestamp":"..."}。 - 然后用
curl测试一个技能调用,确认端到端链路畅通。
实操心得:第一次部署失败,90%的原因是环境变量配置错误,尤其是
DB_HOST和DB_PASSWORD。Railway的错误日志里通常会显示Error: connect ECONNREFUSED或password authentication failed,这时不要慌,回到Variables页面,逐字核对。一个常见的低级错误是把DB_PASSWORD里的特殊字符(如@、/)直接复制过来,而没有进行URL编码,导致连接字符串解析失败。遇到这种情况,可以把密码改成一个只含字母数字的简单密码再试。