# opencode-cloud **Repository Path**: Lemon-9/opencode-cloud ## Basic Information - **Project Name**: opencode-cloud - **Description**: OpenCode 云端发布栈 — 写文件即上线 - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-15 - **Last Updated**: 2026-09-15 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # OpenCode 云端发布栈 — 写文件即上线 单 Docker Compose 双服务:**opencode 云端实例(密码保护、512MB 上限)+ nginx(直接以 `pages/` 为站点根目录)**。宿主机只共享 `pages/` 一个目录,其余全部关在容器与命名卷里。 对照你提的 7 点,逐条落地情况: | 你的要求 | 实现方式 | |---|---| | 1. 云端托管 opencode,本地通过域名连接 | `opencode serve` 常驻在容器里,端口只绑定 `127.0.0.1`,在 **1Panel 里建反向代理**按域名暴露;本地 `opencode run --attach https://ai.你的域名` | | 2. nginx root 直指项目 `pages/`,免发布 | nginx 把 `./pages` 挂成站点根目录(只读),Agent 存盘即上线,**无需 deploy 脚本** | | 3. 单 compose、只挂载 pages | 只有 `./pages` 是宿主机绑定挂载;配置/状态/缓存全在 Docker 命名卷 | | 4. 容器无 root | 官方镜像本身以非 root 运行;compose 再叠加 `cap_drop: ALL` + `no-new-privileges` | | 5. 不需要 list_pages 脚本 | nginx `autoindex` 自动生成根目录页面列表 | | 6. 限制 512MB | `mem_limit: 512m` + `memswap_limit: 512m` + `NODE_OPTIONS=--max-old-space-size=384` | | 7. opencode 密码保护 | `OPENCODE_SERVER_PASSWORD`(HTTP Basic Auth,用户名默认 `opencode`) | ## 目录结构 ``` opencode-cloud/ ├── docker-compose.yml # 双服务定义 ├── .env.example # 复制为 .env 并填写 ├── config/opencode.json # 容器内 opencode 配置(只读挂载) ├── nginx/default.conf # nginx 站点配置(非 root 镜像,监听 8080) ├── AGENTS.md # 容器内 Agent 的行为规则(只读挂载) ├── .opencode/commands/ # 自定义命令:/newpage /pages(只读挂载) └── pages/ # 唯一共享目录 = 站点根目录 └── demo/index.html ``` ## 一、部署(服务器上) ```bash # 1. 放置项目(推荐路径;若用 1Panel「容器 → 编排」创建,则放到 # /opt/1panel/docker/compose/opencode-stack/,否则 compose 里的相对路径 ./ 会指错) sudo mkdir -p /opt/opencode-stack && cd /opt/opencode-stack # 把本目录内容拷进来 # 2. 确认容器用户与 HOME(官方镜像应为 coder / /home/coder) mkdir -p pages docker compose run --rm --entrypoint sh opencode -c 'id; echo HOME=$HOME' # 输出示例:uid=1000(coder) gid=1000(coder) / HOME=/home/coder # 若 HOME 不是 /home/coder,把 compose 中三处 /home/coder/... 改成实际路径 sudo chown -R 1000:1000 pages # 按上一步实际 uid 调整 # 3. 配置 cp .env.example .env sed -i "s|OPENCODE_SERVER_PASSWORD=.*|OPENCODE_SERVER_PASSWORD=$(openssl rand -base64 24)|" .env # 需要模型 Key 就取消 .env 中对应行的注释 # 4. 启动 docker compose up -d docker compose ps docker compose logs -f opencode ``` 首次 `docker compose run --rm opencode id` 会拉取镜像;若 ghcr.io 拉取被拒,先 `docker login ghcr.io`(用户名任意、密码用 GitHub PAT,勾选 `read:packages`)。 **验证密码生效**:`curl -i -u opencode:<密码> http://127.0.0.1:4096/` 应返回非 401。 **固定版本**:把 `.env` 里 `OPENCODE_IMAGE` 改成具体 tag(如 `...:1.4.7`),避免 `latest` 漂移。 ## 二、1Panel 反向代理两个域名 两个端口都只绑在 `127.0.0.1`,所以在 1Panel 面板里各建一个反向代理就完事,HTTPS 也用 1Panel 一键签发——**不需要手写宿主机 nginx 配置**。 **① opencode 域名(如 `ai.example.com`)** 1. `网站 → 网站 → 创建网站 → 反向代理` - 主域名:`ai.example.com` - 代理地址:`http://127.0.0.1:4096` 2. 创建后进该网站的「配置文件」,把 `location /` 段改成下面这样(**流式输出必须关缓冲 + 长超时**,这是唯一需要手工补的地方): ```nginx location / { proxy_pass http://127.0.0.1:4096; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; client_max_body_size 20m; proxy_buffering off; proxy_read_timeout 3600s; proxy_send_timeout 3600s; } ``` 3. `网站 → HTTPS` → 申请证书(Let's Encrypt)→ 开启强制 HTTPS。 **② 页面站点域名(如 `site.example.com`)** 1. `网站 → 创建网站 → 反向代理`,主域名 `site.example.com`,代理地址 `http://127.0.0.1:8080`。 2. 默认配置即可,无需改动;同样去 HTTPS 申请证书。 **要点与取舍** - 端口只绑回环,即使 1Panel 反代配错,opencode 也不会被直接暴露到公网;公网只放行 80/443 即可。 - 1Panel 的「容器」模块里能直接看到 `opencode-stack` 编排的日志、资源占用并做重启,不用敲 `docker compose` 命令。 - **关于能否干掉 web 容器**:1Panel 自带的 OpenResty 也是跑在容器里的,只能访问 `/opt/1panel/www` 下已挂载的内容——所以「用 1Panel 静态网站直接托管 `pages/`」需要把页面目录放进站点目录(例如 `/opt/1panel/www/sites/site.example.com/index`),再把这个真实路径挂给 opencode 容器。当前方案(保留 web 容器 + 反代 8080)与 1Panel 的目录结构解耦、互不依赖升级,更省心,建议保持。 ## 三、本地使用 ```bash # 单次任务(可脚本化 / cron / CI) opencode run --attach https://ai.example.com \ --username opencode --password '<你的密码>' \ "在 pages/tools 下做一个单位换算页面,保存后告诉我访问地址" # 想每次少输参数,可设置环境变量 export OPENCODE_SERVER_PASSWORD='<你的密码>' opencode run --attach https://ai.example.com --username opencode "…" ``` - 会话与状态存在命名卷里,断线重连可继续同一 session(`--session ` / `-c` 续上次)。 - **想要浏览器界面**:把 compose 里 opencode 的 `command` 从 `["serve", ...]` 改成 `["web", "--hostname", "0.0.0.0", "--port", "4096"]`,然后直接访问 `https://ai.example.com`(同样受密码保护)。 - 无人值守自动执行(不弹确认):`opencode run --auto …`;或在 `config/opencode.json` 里按官方文档配置 permission 策略(默认保守更安全)。 ## 四、安全边界 - **可写共享面只有 `pages/`**:容器被攻破时,能改的是一份本来就公开的静态文件,碰不到宿主机其他目录。 - **容器侧**:非 root 用户 + `cap_drop: ALL` + `no-new-privileges` + `mem_limit` + `pids_limit` + CPU 上限。 - **nginx 侧**:`pages` 以 `:ro` 挂载(被攻破也改不了内容),非 root 镜像(监听 8080,无需特权端口),禁止隐藏文件访问。 - **网络面**:容器端口只绑回环,公网只经 1Panel 的反向代理;opencode 走 Basic Auth 密码。 - **密钥**:模型 Key 通过 `.env` 注入环境变量,不落盘到共享目录;`.env` 已在 `.gitignore` 里。 - **注意**:`pages/` 下任何文件都会被公网访问,别在里面放密钥、草稿、日志(AGENTS.md 已明确要求 Agent 遵守)。 ## 五、运维备忘 日常操作建议直接用 **1Panel 的「容器 → 编排」**:能看到 `opencode-stack` 两个容器的日志、CPU/内存曲线,并做重启/停止。等价的命令行操作: ```bash docker compose logs -f opencode # 看 Agent 输出 docker compose restart opencode # 改完 .env / config 后 docker compose exec opencode sh # 进容器排查(非 root) docker compose pull && docker compose up -d # 升级镜像 # 备份会话与状态(命名卷) docker run --rm -v opencode-cloud_opencode-data:/d -v "$PWD":/b alpine tar czf /b/opencode-data.tar.gz -C /d . ``` - **500 报错 / 容器被杀**:多半是内存顶到 512MB。临时提到 768m 观察,或让 Agent 少读大文件(2c2g 机器请优先保证宿主机余量)。 - **页面 404**:URL 要带末尾斜杠(`/demo/`);确认 `pages//index.html` 存在且属主对容器用户可读。 - **opencode 起不来 / 一直重启**:`docker compose logs opencode` 看报错;常见是 `pages` 属主不对(写不进去)或 `.env` 缺 `OPENCODE_SERVER_PASSWORD`(compose 会直接拒绝启动)。 - **改样式不生效**:静态资源 7 天缓存,强刷或加 `?v=2`。 - **下线页面**:直接删 `pages//` 目录(容器内或宿主机都行)。 - **1Panel 里升级/迁移前**:先备份上面的命名卷;`pages/` 按普通目录备份即可。 ## 六、与旧方案的差异 前一版 `opencode-publisher/`(本地生成 + rsync 发布)已被本方案取代:不再需要 `deploy.sh`、`list_pages.sh`、目录首页生成逻辑,`publisher.env` 也无需服务器地址——因为"发布"这个动作整个消失了。旧目录可以删除。