首页
/ Puter:开源「互联网计算机」的本地开发、自托管与架构解析

Puter:开源「互联网计算机」的本地开发、自托管与架构解析

2026-09-05 19:25:49作者:曹令琨Iris

本文基于 Puter 仓库的 README.md 及其配套的安装脚本、Docker 编排与自托管文档展开,讲解 Puter 这一「浏览器里的桌面环境 / 个人云计算机」项目的三种落地方式:源码本地开发、Docker Compose 全栈自托管(含一行式安装器)、以及生产托管服务。读完你可以独立完成 Puter 的克隆构建、单主机多容器部署、DNS 通配符与 TLS 配置,并能看懂 Caddy 反向代理、MariaDB/Valkey/DynamoDB-local/RustFS 各组件在整体栈中的角色。

一、Puter 是什么

Puter 的自我定位是一个先进、开源、可自托管的「互联网计算机(Internet Computer)」:功能丰富、速度快、高度可扩展(README.md)。它把文件、应用、游戏收纳在一处,从任何设备都能访问。

README 把读者分成两类:

  • 面向用户:Puter 的目标是让所有工作、创作、娱乐所需的应用与功能集中在一个入口下——从记事本、录音机到电子表格、摄像头,一站式覆盖数字生活。
  • 面向开发者:Puter 提供构建与发布 Web 应用、游戏所需的一切能力:AI、云存储(对象存储)、数据库(KV)、Serverless Workers;应用发布到 App Store 后还能触达并变现用户。

仓库本身是一个 npm workspaces 单仓库(monorepo),package.json"workspaces": ["src/*"] 声明了子项目结构:

目录 职责
src/gui 浏览器桌面环境 GUI(webpack 打包、nodemon 热重载)
src/backend Node.js/TypeScript 后端:controllers / services / stores / clients / drivers 分层架构
src/puter-js 供第三方网页嵌入的 SDK(puter.js),附带 API/e2e 测试
src/worker Serverless Worker 运行时库构建
src/docs 开发者文档站
src/mcp-connector MCP 连接器
extensions 后端扩展(thumbnails、metering、whoami、serverInfo 等)
tools 启动器、类型检查、覆盖率报告等工程脚本

后端的分层与依赖注入细节(Controllers → Drivers → Services → Stores → Clients → Config,由 PuterServer 依次实例化)在 doc/architecture.md 中有完整说明,本文只在自托管语境下引用其结论。

二、本地开发:npm start 起一个完整后端

README 给出的本地开发流程是:

git clone https://gitcode.com/GitHub_Trending/pu/puter
cd puter
npm install
npm start

成功后 Puter 会运行在 http://puter.localhost:4100

结合仓库源码,这条流程的实际行为是:

  • Node 版本要求package.json 声明 "engines": { "node": ">=24.0.0" },且 Docker 镜像也基于 node:24-slim,即 Node 24+ 是明确前提。
  • npm start 的入口tools/start.mjs。默认路径(不带参数)依次执行 setupExtensionsbuild:tstsc -p tsconfig.build.json 编译后端),然后以 node -r ./dist/src/backend/telemetry.js ./dist/src/backend/index.js 启动自托管后端——也就是说本地开发起的是与生产自托管同一套代码路径。
  • --server 参数npm start --server=puter.com(或任意完整 origin)会完全跳过本地后端,只在本机用 src/gui/dev-server.js 起 GUI,并对接远端 Puter 后端;裸域名会按 https://api.<domain> 约定解析。这是前端开发者调试远程环境的方式。
  • --extensions 参数npm start --server=puter.com --extensions=<dir>;<dir> 可把仓库外的 GUI 扩展目录捆绑进本地 GUI(等价于 PUTER_GUI_EXTENSION_PATHS),仅限 GUI-only 模式使用。

测试方面,package.json 提供了 test:backend(vitest,默认引擎)、test:backend:postgresPUTER_TEST_DB_ENGINE=postgres)、test:puterjs(node / browser / workerd 三种 runner)等脚本,可作为验证本地环境是否搭对的依据。

三、自托管:一行式安装器

README 给出的自托管命令:

  • Linux/macOS:
curl -fsSL https://puter.com/selfhost | sh
  • Windows(PowerShell):
irm https://puter.com/selfhost?os=windows | iex

这两个 URL 最终执行的逻辑与仓库内的 install.sh / install.ps1 一致(也可从仓库 raw 地址直接拉取脚本)。脚本的执行步骤(install.sh 头注释即为权威说明):

  1. 检查依赖:docker(含 compose 插件)、curlopenssl
  2. 创建安装目录 ./puter-selfhosted/(可用 PUTER_DIR 覆盖),预建 puter/data/{valkey,mariadb,dynamo,s3,puter,caddy} 等目录并放宽权限,规避非 root 容器在原生 Linux 上的 EACCES 问题;
  3. 从 OSS 仓库下载 docker-compose.ymlcaddy/Caddyfile
  4. openssl rand 生成全套随机密钥,写入 .envputer/config/config.json
  5. docker compose up -d 启动,并提示用 docker compose logs puter | grep password 抓取首次启动打印的一次性 admin 临时密码。

可调整的环境变量(install.sh):

变量 作用 默认值
PUTER_DIR 安装目录 ./puter-selfhosted
PUTER_URL 拉取 compose 文件的基础 URL GitHub raw(main 分支)
PUTER_DOMAIN Puter 服务的域名 puter.localhost
PUTER_PORT Caddy 的 HTTP 端口 80
PUTER_PROTOCOL 对外协议 http/https(有 TLS 终结时设 https) http
PUTER_TRUST_PROXY 前置反向代理跳数(1=内置 Caddy;2=Cloudflare→Caddy→Puter) 1
PUTER_ENV prod/dev prod
PUTER_FORCE 设为 1 时覆盖已有 .env/config.json 0

生成的密钥包括 MARIADB_ROOT_PASSWORD / MARIADB_PASSWORD / S3_SECRET_KEY(各 32 字节 hex),以及 jwt_secret(仅用于验证存量 v1 旧 token)、jwt_secret_v2(签发所有新 token)、URL_SIGNATURE_SECRET(各 64 字节 hex)。重复执行脚本是安全的——已存在的 .envconfig.json 不会被覆盖,只会刷新 compose 文件并拉起栈。

自托管的完整手动步骤(.envconfig.json 全量示例、DNS、TLS、反向代理规则、故障排查)在 doc/self-hosting.md,以下各节将其与仓库文件结合展开。

四、Docker 全栈:七个容器各司其职

doc/self-hosting.mddocker-compose.yml 的概括是:拉起 Puter 及其所需的全部外部服务,是单主机上最接近生产形态的部署。容器清单:

容器 镜像 角色
puter-caddy caddy:2.11-alpine 80/443 反向代理,转发到 Puter
puter ghcr.io/heyputer/puter 应用本体(端口 4100)
puter-mariadb mariadb:11 SQL 数据库,首次启动自动应用 schema
puter-valkey valkey/valkey:8-alpine Redis 兼容缓存 + 限流后端
puter-dynamo amazon/dynamodb-local KV 存储,KV 表首次启动自动创建
puter-s3 rustfs/rustfs S3 兼容对象存储(注释中注明 MinIO 可平替)
puter-s3-init amazon/aws-cli 一次性容器,首启创建 puter-local bucket 后退出

所有状态统一落在 ./puter/data/<service>/,一个目录就是全部备份范围。

几个值得注意的实现细节(均来自 docker-compose.yml 注释与编排内容):

  • Valkey 以单节点集群模式运行:Puter 的 ioredis 客户端只支持 Cluster 模式,所以编排里用 --cluster-enabled yes 启动,并在首启时把 16384 个槽位全部分配给自己,--cluster-require-full-coverage no 保证部分槽位缺失时读仍可用。
  • DynamoDB-localbootstrapTables: true 让 Puter 在启动时自建 store-kv-v1 表;容器固定 user: "1000:1000",与 install.sh 中把 bind-mount 数据目录设为 0777 的处理相呼应,都是为了让不同 UID 的容器能写入宿主机目录。
  • RustFS(S3):默认不发布宿主机端口,浏览器侧的预签名上传/下载全部经 Caddy 的 s3.<domain> 子域名转发(保持 Host 头以通过 S3 签名校验);需要 aws-cli 调试时可手动解开 9000:9000 映射。
  • 启动依赖链puter 服务 depends_on 中等待 valkey/mariadb 健康、dynamo 启动、s3-init 成功退出;caddy 再依赖 puter 启动。首次启动约 30 秒,主要是 MariaDB 初始化。

五、手动自托管:.envconfig.json 逐键解析

如果不用一行式安装器,doc/self-hosting.md 的 Step 1 要求在同一个 shell 会话中完成密钥生成与两份配置写入——.env(docker compose 读取)和 puter/config/config.json(Puter 读取)必须对 MariaDB 密码与 S3 密钥保持一致,否则会出现 ER_ACCESS_DENIED_ERROR

.env 内容(安装器生成的同形):

MARIADB_ROOT_PASSWORD=$(openssl rand -hex 32)
MARIADB_PASSWORD=$(openssl rand -hex 32)
S3_SECRET_KEY=$(openssl rand -hex 32)
JWT_SECRET_V2=$(openssl rand -hex 64)
URL_SIGNATURE_SECRET=$(openssl rand -hex 64)

# .env
HTTP_PORT=80
# HTTPS_PORT=443     # 启用 TLS 后再取消注释

MARIADB_ROOT_PASSWORD=$MARIADB_ROOT_PASSWORD
MARIADB_DATABASE=puter
MARIADB_USER=puter
MARIADB_PASSWORD=$MARIADB_PASSWORD

S3_ACCESS_KEY=puter
S3_SECRET_KEY=$S3_SECRET_KEY
S3_BUCKET=puter-local

puter/config/config.json 骨架(一行式安装器按 install.sh 模板写入的版本,含全部默认项):

{
    "domain": "puter.localhost",
    "protocol": "http",
    "pub_port": 80,
    "env": "prod",

    "static_hosting_domain": "site.puter.localhost",
    "static_hosting_domain_alt": "host.puter.localhost",
    "private_app_hosting_domain": "app.puter.localhost",
    "private_app_hosting_domain_alt": "dev.puter.localhost",

    "jwt_secret": "...",
    "jwt_secret_v2": "...",
    "url_signature_secret": "...",

    "database": {
        "engine": "mysql",
        "host": "mariadb",
        "port": 3306,
        "user": "puter",
        "password": "...",
        "database": "puter",
        "migrationPaths": ["/opt/puter/dist/src/backend/clients/database/migrations/mysql"]
    },
    "redis": { "startupNodes": [{ "host": "valkey", "port": 6379 }], "tls": false },
    "dynamo": {
        "endpoint": "http://dynamo:8000",
        "bootstrapTables": true,
        "aws": { "access_key": "fake", "secret_key": "fake", "region": "us-east-1" }
    },
    "s3": {
        "s3Config": {
            "endpoint": "http://s3:9000",
            "publicEndpoint": "http://s3.puter.localhost",
            "accessKeyId": "puter",
            "secretAccessKey": "...",
            "region": "us-east-1",
            "forcePathStyle": true
        }
    },
    "s3_bucket": "puter-local",
    "s3_region": "us-east-1",
    "providers": { "ollama": { "enabled": false } },
    "meteringEnforcement": { "subscriptions": false },
    "trust_proxy": 1
}

关键配置项的设计动机(doc/self-hosting.md 的 "Why these knobs" 一节):

  • jwt_secret_v2:Puter 签发/验证认证 token 的 HMAC 密钥(JWT header kid: 'v2');旧版 jwt_secret 只用于验证存量 v1 token,若从老版本升级应直接删除旧字段。
  • env: "prod":内置 config.default.json 默认 env: "dev"(配合 webpack-dev-server 的 CSS manifest 工作流);自托管跑的是预构建静态包,须设 prod 让首页输出 /dist/bundle.min.css
  • database.migrationPaths:启动时应用内置 MySQL/MariaDB schema;迁移文件幂等,重启安全。
  • dynamo.bootstrapTables: true:只用于本地模拟器,绝不可指向真实 AWS。
  • dynamo.awss3.s3Config 的命名风格不同:前者 snake_case(access_key/secret_key),后者 camelCase(accessKeyId/secretAccessKey),两者不可互换——写错正是排障清单里的经典错误。
  • meteringEnforcement.subscriptions: false:OpenAI/Anthropic 兼容的 AI 端点被声明为付费计划专属;自托管没有付费计划,若保留该门禁所有人都会收到 402 subscription_required
  • providers.ollama.enabled: false:默认 Puter 会探测 127.0.0.1:11434 的本地 Ollama,没有它会每次启动刷 ECONNREFUSED
  • s3.s3Config.forcePathStyle: true:RustFS/MinIO 需要 path-style URL;换真实 AWS S3 时去掉此标志,publicEndpoint 也可整体删除。
  • trust_proxy: 1:Caddy 终结 TLS 并转发 X-Forwarded-For,不设它 req.ip 会是 Caddy 容器地址,限流与 IP 审计即失效。前置第二层代理(如 Cloudflare)时改为 2永远不要设 true(信任所有跳,XFF 可伪造)。该键的完整语义见 config.template.jsonctrust_proxy 的注释。
  • 密码轮换注意:首启后改 MARIADB_PASSWORD,仅改 .env 不会更新 MariaDB(凭据已固化进 ./puter/data/mariadb/),需在库内手动改密或清空该数据目录重来。

全部可配置键的权威清单在 config.template.jsonc(每个键带注释,未设置的键回落到文档化默认值),逐字段类型定义在 src/backend/types.ts

六、DNS 通配符与 TLS

Puter 按子域名路由:api.<domain>site.<domain>app.<domain>dev.<domain> 及每个用户的 <name>.site.<domain> 等动态子域名,因此 doc/self-hosting.md 要求为每个托管域配通配符 A 记录(*.<domain>*.site.<domain>*.host.<domain>*.app.<domain>*.dev.<domain>),外加 s3.<domain>

TLS 方面,官方建议用 Let's Encrypt DNS-01 申请通配符证书certbot certonly --manual --preferred-challenges dns -d <domain> -d *.<domain> …),把 fullchain.pem + privkey.pem 放进 ./puter/tls/,然后:

  1. 解开 caddy/Caddyfile 底部的 :443 块,并把 :80 块换成强制跳转的 redir 版本;
  2. 解开 docker-compose.yml 中 caddy 服务的 443:443 端口映射;
  3. .env 解开 HTTPS_PORT=443
  4. config.json 设为 "protocol": "https", "pub_port": 443,并把 S3 publicEndpoint 改为 https://s3.<domain>

为什么不用 Caddy 的自动 HTTPS:标准 caddy:2.11-alpine 镜像只支持对已知主机名做 HTTP-01 签发,而 Puter 会在运行时发明 <name>.site.<domain> 这类子域名,只能靠 DNS-01 通配符证书覆盖;DNS-01 需要的 DNS 提供商插件不在标准镜像中。所以 caddy/Caddyfile 里显式写了 auto_https off,证书完全由运维方提供。

七、Caddy 反向代理配置解析

caddy/Caddyfile 镜像了生产环境 ALB 的行为:接受任意 Host 头、原样转发给 Puter,子域名路由全部由 Puter 内部完成。核心逻辑封装在共享片段 (puter_routes) 中:

  • request_body max_size 1024MiB:粗略对齐生产 ALB 的请求体上限;Puter 大文件上传是分块的,单请求 1 GiB 足够。
  • @s3 header_regexp Host ^s3\.reverse_proxy s3:9000:只匹配 s3. 开头的 Host,转发到 RustFS;Caddy 全程保留原始 Host 头,S3 签名校验因此成立,且与 Puter 共享同一 TLS 终结,避免 9000 端口直连带来的混合内容问题。
  • 其余一切 Host → reverse_proxy puter:4100,并设 flush_interval -1 让 SSE/socket.io 流式响应直通;WebSocket 升级 Caddy 默认代理,无需额外配置。
  • :80 { import puter_routes }:无主机名的 site 地址是 catch-all,对每个 Host 应答——这正是 Puter 子域名路由所需。

若把 Puter 放到自己的边缘代理(Traefik/nginx/云 LB)后面,doc/self-hosting.md 列出六条硬性规则,违反任何一条都会造成重定向循环或 Invalid Host header:不改写 Host 头、外部域名必须等于 config.jsondomainprotocol 与对外协议一致、转发 X-Forwarded-Proto/X-Forwarded-For/Upgrade/Connectiontrust_proxy 等于实际代理跳数、通配符子域名流量全部路由到 Puter。

八、Docker 镜像与配置注入机制

自托管默认拉取 ghcr.io/heyputer/puter:mainpull_policy: always);Dockerfile 是 multistage 构建:node:24-slim 构建阶段先拷 package 清单再 npm ci(利用层缓存),然后 npm run build:ts 编译后端、并行构建 GUI 与 puter-js 的 webpack bundle;运行阶段是无构建工具的 slim 镜像,以 node 用户运行,EXPOSE 4100,健康检查为 wget --spider http://puter.localhost:4100/test

配置注入的关键约定(Dockerfile 注释):自托管者把 config.json 挂载到 /etc/puter/config.json(对应 compose 里的 ./puter/config:/etc/puter),配置加载器会将其深度合并在内置 config.default.json 之上,所以部分覆盖是允许的,文件不存在则全用默认值。

想从源码构建本地镜像:解开 docker-compose.ymlputer 服务的 build: 块(可加 platforms: [linux/amd64, linux/arm64] 交叉编译),并把 pull_policy 改为 never,然后 docker compose up -d --build

九、可选本地 LLM 与其他进阶配置

doc/self-hosting.md 的「Additional configuration」各块均可直接并入 puter/config/config.jsondocker compose restart puter

  • PostgreSQL:社区贡献、非生产默认(生产自托管推荐 MariaDB/MySQL 与 SQLite)。把 database.engine 改为 postgres 并把 migrationPaths 指向 /opt/puter/dist/src/backend/clients/database/migrations/postgres,也可改用 connectionString
  • 邮件(SMTP):用于密码重置、邮箱确认与通知;不配置时这些流程会静默失败。示例块含 from/host/port/secure/auth;可配 "strict_email_verification_required": true 强制登录前验证邮箱;本地调试可用 MailHog 容器(docker run -d -p 1025:1025 -p 8025:8025 mailhog/mailhog)作 SMTP 汇。
  • OIDC 登录:Google(client_id/client_secret/scopes,走 OIDC 发现)、Apple(client_id/team_id/key_id/private_key)、Microsoft(client_id/client_secret/tenant_id)三类示例配置,回调地址统一为 https://puter.<domain>/auth/oidc/callback/login(及 /signup);自定义 OIDC 供应商需显式给出 authorization_endpoint/token_endpoint/userinfo_endpoint
  • AI 供应商:任何设置了 apiKey 的供应商自动启用,如 claudeopenai-completiongeminiopenai-image-generation;完整供应商清单(chat/image/video/TTS/OCR)见 config.template.jsonc
  • 存储配额storage_capacity(默认 100 MB)+ is_storage_limited(设 false 为无限,受宿主机磁盘限制)。
  • 计量与预算:Puter 按每月预算计量下行流量、对象存储请求、KV 容量与 AI token,超限返回 402 insufficient_funds(只读与删除操作始终可用)。自托管没有购买渠道,建议 "unlimitedMetering": true(账户全解析为无限策略,用量仍记录);或 "meteringEnforcement": { "enabled": false } 只记录不拦截;worker 发起的调用默认豁免执行,"meteringEnforcement": { "workers": true } 可纳入。
  • Captcha:内置 proof-of-work 验证码,"captcha": { "enabled": true, "difficulty": "medium" },难度 easy/medium/hard
  • 关闭注册"disable_user_signup": true 强制访客用已有账户登录。
  • 一次性邮箱 TLD 屏蔽blockedEmailDomains 列表,仅在 env: "prod" 时生效。
  • 密码策略"min_pass_length": 12
  • 联系表收件人"support_email",默认 support@puter.com

本地 LLM(Ollama)ollamaollama-init 两个服务在 compose 的 ai profile 之后,不会默认启动。启用步骤:在 config.json"providers": { "ollama": { "apiBaseUrl": "http://ollama:11434" } }(不启用时保持 "enabled": false,否则启动刷 ECONNREFUSED);在 .env 可选设置 OLLAMA_DEFAULT_MODEL(默认 tinyllama,约 640 MB 磁盘 / 700 MB 内存);然后 docker compose --profile ai up -dollama-init 是一次性拉模型容器,模型已在盘上时 pull 是快速 no-op。NVIDIA GPU 直通需宿主机装 nvidia-container-toolkit 并解开 compose 中 deploy: 块。

十、日常运维与故障排查

日常管理命令(doc/self-hosting.md):

# 更新
docker compose pull
docker compose up -d

# 日志
docker compose logs -f puter

# 停止但保留数据
docker compose down

# 停止并清除全部状态(不可逆)
docker compose down
rm -rf puter/data

迁移在 pull 更新后幂等重放,卷会保留。

现象 原因与处置
Caddy 502 / Bad Gateway puter 容器没起来;docker compose logs puter 看是哪个依赖拒绝(最常见是 .envconfig.json 的 DB 密码不一致)
登录页提示 admin 密码未设置 首启临时密码只打印一次:`docker compose logs puter
健康检查不健康但站点正常 镜像内 HEALTHCHECK 打的是默认域/端点;若改过 domain/port 需留意,站点本身无碍
DNS 改完不解析 等待传播(5–60 分钟),dig <domain>dig api.<domain> 都应返回服务器 IP
compose up 卡在等健康 docker compose ps 看谁不健康;MariaDB 冷启约 20–30s,其余均 <5s
DynamoDB aws config requires both access_key and secret_key dynamo.aws 里用了 camelCase;该块必须是 snake_case(access_key/secret_key),只有 s3.s3Config 用 camelCase

十一、社区、许可与多语言

README 的 Support 一节给出与社区联系的渠道:Bug 报告与功能请求走 issue;X (Twitter) @HeyPuter;安全问题/滥用举报 security@puter.com;维护者邮箱 hi@puter.com

许可方面:本仓库及其全部内容、子项目、模块与组件均按 AGPL-3.0 许可(LICENSE.txt),除非另有明确声明;仓库内第三方库可能适用各自许可。

README 还维护了 30+ 语言的多语言版本索引(阿拉伯语、孟加拉语、中文、丹麦语、英语、波斯语、芬兰语、法语、德语、希伯来语、印地语、匈牙利语、印尼语、意大利语、日语、韩语、马来语、荷兰语、波兰语、葡萄牙语、罗马尼亚语、俄语、西班牙语、瑞典语、泰语、土耳其语、乌克兰语、乌尔都语、越南语等),对应文件位于 doc/i18n/ 目录(如 doc/i18n/README.zh.md)。

小结

Puter 的仓库结构让「开发者体验」与「自托管体验」走同一条代码路径:本地 npm start 与 Docker 栈启动的是同一套后端(dist/src/backend/index.js),配置都收敛到 config.json 的深度合并语义上。掌握本文的三个抓手——tools/start.mjs 的开发启动链路、docker-compose.yml + caddy/Caddyfile 的单主机全栈、以及 doc/self-hosting.md 的子域名路由与 TLS 规则——即可在一个主机上完成从克隆到生产形态自托管的完整闭环。

登录后查看全文
热门项目推荐
相关项目推荐